personality background image and background color
This commit is contained in:
@@ -38,6 +38,7 @@
|
|||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.4",
|
||||||
"three": "^0.177.0",
|
"three": "^0.177.0",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+9
@@ -89,6 +89,9 @@ importers:
|
|||||||
three:
|
three:
|
||||||
specifier: ^0.177.0
|
specifier: ^0.177.0
|
||||||
version: 0.177.0
|
version: 0.177.0
|
||||||
|
uuid:
|
||||||
|
specifier: ^11.1.0
|
||||||
|
version: 11.1.0
|
||||||
vite-tsconfig-paths:
|
vite-tsconfig-paths:
|
||||||
specifier: ^5.1.4
|
specifier: ^5.1.4
|
||||||
version: 5.1.4(typescript@5.7.3)(vite@6.3.5(@types/node@24.0.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
|
version: 5.1.4(typescript@5.7.3)(vite@6.3.5(@types/node@24.0.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
|
||||||
@@ -2835,6 +2838,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
|
resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
|
uuid@11.1.0:
|
||||||
|
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
validator@13.15.15:
|
validator@13.15.15:
|
||||||
resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==}
|
resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
@@ -5920,6 +5927,8 @@ snapshots:
|
|||||||
|
|
||||||
utility-types@3.11.0: {}
|
utility-types@3.11.0: {}
|
||||||
|
|
||||||
|
uuid@11.1.0: {}
|
||||||
|
|
||||||
validator@13.15.15: {}
|
validator@13.15.15: {}
|
||||||
|
|
||||||
vite-tsconfig-paths@5.1.4(typescript@5.7.3)(vite@6.3.5(@types/node@24.0.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)):
|
vite-tsconfig-paths@5.1.4(typescript@5.7.3)(vite@6.3.5(@types/node@24.0.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)):
|
||||||
|
|||||||
+3
-1
@@ -109,12 +109,14 @@
|
|||||||
"add_address": "اضافه کردن نشانی",
|
"add_address": "اضافه کردن نشانی",
|
||||||
"domain1": "دامنه",
|
"domain1": "دامنه",
|
||||||
"create": "ساخت",
|
"create": "ساخت",
|
||||||
|
"header_email": "هدر ایمیل شما",
|
||||||
"content_email": "محتوای ایمیل شما",
|
"content_email": "محتوای ایمیل شما",
|
||||||
"footer_email": "پاورقی ایمیل شما",
|
"footer_email": "پاورقی ایمیل شما",
|
||||||
"signature": "امضا",
|
"signature": "امضا",
|
||||||
"signature_description": "تصویر و توضیحات امضای خود را با دقت وارد کنید",
|
"signature_description": "تصویر و توضیحات امضای خود را با دقت وارد کنید",
|
||||||
"upload_sign": "آپلود امضا",
|
"upload_sign": "آپلود امضا",
|
||||||
"description_sign": "توضیحات امضا"
|
"description_sign": "توضیحات امضا",
|
||||||
|
"select_section": "لطفا یک بخش انتخاب کنید"
|
||||||
},
|
},
|
||||||
"select_file": "انتخاب فایل",
|
"select_file": "انتخاب فایل",
|
||||||
"upload": "آپلود",
|
"upload": "آپلود",
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import Button from '@/components/Button'
|
import Button from '@/components/Button'
|
||||||
import { ArrowRight, InfoCircle, SmsNotification, Star, VolumeMute, More, DocumentDownload, ArchiveTick, Printer, Trash } from 'iconsax-react'
|
import { ArrowRight, InfoCircle, SmsNotification, Star, VolumeMute, More, DocumentDownload, ArchiveTick, Printer, Trash } from 'iconsax-react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const Header: FC = () => {
|
const Header: FC = () => {
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
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 xl:gap-4 gap-2'>
|
<div className='flex xl:gap-4 gap-2'>
|
||||||
<ArrowRight size={18} color='#8C90A3' />
|
<ArrowRight size={18} color='#8C90A3' onClick={() => navigate(-1)} />
|
||||||
<SmsNotification size={18} color='black' className='xl:block hidden' />
|
<SmsNotification size={18} color='black' className='xl:block hidden' />
|
||||||
<ArchiveTick size={18} color='black' className='xl:block hidden' />
|
<ArchiveTick size={18} color='black' className='xl:block hidden' />
|
||||||
<InfoCircle size={18} color='black' className='xl:block hidden' />
|
<InfoCircle size={18} color='black' className='xl:block hidden' />
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const Domain: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-9'>
|
<div className='mt-9'>
|
||||||
<div className='flex xl:flex-row gap-4 flex-col justify-between xl:items-end pb-10'>
|
<div className='flex xl:flex-row gap-4 flex-col justify-between pb-10'>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<div className='xl:text-lg'>
|
<div className='xl:text-lg'>
|
||||||
{t('setting.setting_smtp')}
|
{t('setting.setting_smtp')}
|
||||||
|
|||||||
@@ -1,30 +1,18 @@
|
|||||||
import { AddCircle } from 'iconsax-react'
|
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import HeaderSection from './components/HeaderSection'
|
||||||
|
import ContentSection from './components/ContentSection'
|
||||||
|
import FooterSection from './components/FooterSection'
|
||||||
|
|
||||||
const Personality: FC = () => {
|
const Personality: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex-1 bg-white rounded-4xl p-8'>
|
<div className='flex-1 bg-white rounded-4xl p-8'>
|
||||||
<div className='border flex items-center p-2.5 gap-4 border-dashed border-border h-[123px] rounded-4xl'>
|
<table width="100%" cellPadding="0" cellSpacing="0" border={0} style={{ borderCollapse: 'collapse' }}>
|
||||||
<div className='flex-1 h-[103px] rounded-4xl border border-dashed border-[#0038FF]'></div>
|
<tbody>
|
||||||
<div className='flex-1 h-[103px] rounded-4xl border border-dashed border-border'></div>
|
<HeaderSection />
|
||||||
</div>
|
<ContentSection />
|
||||||
|
<FooterSection />
|
||||||
<div className='mt-4 h-[286px] border border-dashed border-border rounded-4xl flex flex-col items-center justify-center p-2.5'>
|
</tbody>
|
||||||
<div className='text-description'>{t('setting.content_email')}</div>
|
</table>
|
||||||
<div className='mt-1'>
|
|
||||||
<AddCircle size={24} color='#888' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='border flex flex-col justify-center mt-4 items-center p-2.5 gap-4 border-dashed border-border h-[123px] rounded-4xl'>
|
|
||||||
<div className='text-description'>{t('setting.footer_email')}</div>
|
|
||||||
<div className='mt-1'>
|
|
||||||
<AddCircle size={24} color='#888' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,29 @@ import { Checkbox } from '@/components/ui/checkbox'
|
|||||||
import { AlignRight, AlignBottom, AlignHorizontally, AlignLeft, AlignTop, AlignVertically, Link2, Add } from 'iconsax-react'
|
import { AlignRight, AlignBottom, AlignHorizontally, AlignLeft, AlignTop, AlignVertically, Link2, Add } from 'iconsax-react'
|
||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { usePersonalityStore } from '../store/Store'
|
||||||
|
|
||||||
const ButtonSidebar: FC = () => {
|
const ButtonSidebar: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { addButtonToActiveItem } = usePersonalityStore()
|
||||||
const [isBorder, setIsBorder] = useState<boolean>(false)
|
const [isBorder, setIsBorder] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const handleAddButton = () => {
|
||||||
|
addButtonToActiveItem({
|
||||||
|
text: 'دکمه جدید',
|
||||||
|
link: '#',
|
||||||
|
size: 'medium',
|
||||||
|
isBorder: true,
|
||||||
|
borderSize: 1,
|
||||||
|
textColor: '#ffffff',
|
||||||
|
backgroundColor: '#0038FF',
|
||||||
|
borderColor: '#0038FF',
|
||||||
|
alignment: 'center',
|
||||||
|
verticalAlignment: 'middle',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='text-lg'>{t('setting.button')}</div>
|
<div className='text-lg'>{t('setting.button')}</div>
|
||||||
@@ -90,7 +107,7 @@ const ButtonSidebar: FC = () => {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className='text-sm'>
|
<div className='text-sm'>
|
||||||
{t('setting.horizontal_position')}
|
{t('setting.vertical_position')}
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-3 flex gap-4'>
|
<div className='mt-3 flex gap-4'>
|
||||||
<AlignBottom size={20} color='#000' />
|
<AlignBottom size={20} color='#000' />
|
||||||
@@ -102,7 +119,7 @@ const ButtonSidebar: FC = () => {
|
|||||||
|
|
||||||
<div className='mt-7'>
|
<div className='mt-7'>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => null}
|
onClick={handleAddButton}
|
||||||
className='bg-[#ECEEF5] text-black'
|
className='bg-[#ECEEF5] text-black'
|
||||||
>
|
>
|
||||||
<div className='flex justify-center items-center gap-2'>
|
<div className='flex justify-center items-center gap-2'>
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { usePersonalityStore } from '../store/Store'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { AddCircle } from 'iconsax-react'
|
||||||
|
|
||||||
|
const ContentSection: FC = () => {
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { activeSection, activeItemIndex, data, setActiveSection, setActiveItemIndex } = usePersonalityStore()
|
||||||
|
|
||||||
|
const handleSectionClick = (section: "header" | "content" | "footer") => {
|
||||||
|
setActiveSection(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleItemClick = (index: number) => {
|
||||||
|
setActiveItemIndex(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr onClick={() => handleSectionClick("content")}>
|
||||||
|
<td style={{ paddingTop: '16px' }}>
|
||||||
|
{
|
||||||
|
data.content.columnsCount > 0 ?
|
||||||
|
<table width="100%" cellPadding="0" cellSpacing="0" border={0}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style={{
|
||||||
|
padding: '10px',
|
||||||
|
border: activeSection === "content" ? '1px dashed #0038FF' : '1px dashed #E5E7EB',
|
||||||
|
borderRadius: '24px'
|
||||||
|
}}>
|
||||||
|
<table width="100%" cellPadding="0" cellSpacing="0" border={0}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{
|
||||||
|
Array.from({ length: data.content.columnsCount }, (_, index) => {
|
||||||
|
const item = data.content.items[index];
|
||||||
|
const isActive = activeSection === "content" && activeItemIndex === index;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<td
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleItemClick(index);
|
||||||
|
}}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
height: '246px',
|
||||||
|
border: isActive ? '1px dashed #0038FF' : '1px dashed #EAECF4',
|
||||||
|
backgroundColor: item?.backgroundColor || '#ffffff',
|
||||||
|
backgroundImage: item?.backgroundImage ? `url(${item.backgroundImage})` : 'none',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* محتوای آیتم */}
|
||||||
|
{item?.texts?.map(text => (
|
||||||
|
<div key={text.id} style={{ color: text.color }}>
|
||||||
|
{text.text}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{item?.buttons?.map(button => (
|
||||||
|
<button
|
||||||
|
key={button.id}
|
||||||
|
style={{
|
||||||
|
backgroundColor: button.backgroundColor,
|
||||||
|
color: button.textColor
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{button.text}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</td>
|
||||||
|
{index < data.content.columnsCount - 1 && (
|
||||||
|
<td style={{ width: '16px' }}></td>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
:
|
||||||
|
<table width="100%" cellPadding="0" cellSpacing="0" border={0}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style={{
|
||||||
|
height: '286px',
|
||||||
|
border: activeSection === "content" ? '1px dashed #0038FF' : '1px dashed #E5E7EB',
|
||||||
|
borderRadius: '24px',
|
||||||
|
textAlign: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
padding: '10px'
|
||||||
|
}}>
|
||||||
|
<div style={{ color: '#888', fontSize: '14px', marginBottom: '8px' }}>
|
||||||
|
{t('setting.content_email')}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<AddCircle size={24} color='#888' />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContentSection
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { usePersonalityStore } from '../store/Store'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { AddCircle } from 'iconsax-react'
|
||||||
|
|
||||||
|
const FooterSection: FC = () => {
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { activeSection, activeItemIndex, data, setActiveSection, setActiveItemIndex } = usePersonalityStore()
|
||||||
|
|
||||||
|
const handleSectionClick = (section: "header" | "content" | "footer") => {
|
||||||
|
setActiveSection(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleItemClick = (index: number) => {
|
||||||
|
setActiveItemIndex(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr onClick={() => handleSectionClick("footer")}>
|
||||||
|
<td style={{ paddingTop: '16px' }}>
|
||||||
|
{
|
||||||
|
data.footer.columnsCount > 0 ?
|
||||||
|
<table width="100%" cellPadding="0" cellSpacing="0" border={0}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style={{
|
||||||
|
padding: '10px',
|
||||||
|
border: activeSection === "footer" ? '1px dashed #0038FF' : '1px dashed #E5E7EB',
|
||||||
|
borderRadius: '24px'
|
||||||
|
}}>
|
||||||
|
<table width="100%" cellPadding="0" cellSpacing="0" border={0}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{
|
||||||
|
Array.from({ length: data.footer.columnsCount }, (_, index) => {
|
||||||
|
const item = data.footer.items[index];
|
||||||
|
const isActive = activeSection === "footer" && activeItemIndex === index;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<td
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleItemClick(index);
|
||||||
|
}}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
height: '83px',
|
||||||
|
border: isActive ? '1px dashed #0038FF' : '1px dashed #EAECF4',
|
||||||
|
backgroundColor: item?.backgroundColor || '#ffffff',
|
||||||
|
backgroundImage: item?.backgroundImage ? `url(${item.backgroundImage})` : 'none',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* محتوای آیتم */}
|
||||||
|
{item?.texts?.map(text => (
|
||||||
|
<div key={text.id} style={{ color: text.color }}>
|
||||||
|
{text.text}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{item?.buttons?.map(button => (
|
||||||
|
<button
|
||||||
|
key={button.id}
|
||||||
|
style={{
|
||||||
|
backgroundColor: button.backgroundColor,
|
||||||
|
color: button.textColor
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{button.text}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</td>
|
||||||
|
{index < data.footer.columnsCount - 1 && (
|
||||||
|
<td style={{ width: '16px' }}></td>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
:
|
||||||
|
<table width="100%" cellPadding="0" cellSpacing="0" border={0}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style={{
|
||||||
|
height: '123px',
|
||||||
|
border: activeSection === "footer" ? '1px dashed #0038FF' : '1px dashed #E5E7EB',
|
||||||
|
borderRadius: '24px',
|
||||||
|
textAlign: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
padding: '10px'
|
||||||
|
}}>
|
||||||
|
<div style={{ color: '#888', fontSize: '14px', marginBottom: '8px' }}>
|
||||||
|
{t('setting.footer_email')}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<AddCircle size={24} color='#888' />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FooterSection
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { usePersonalityStore } from '../store/Store'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { AddCircle } from 'iconsax-react'
|
||||||
|
|
||||||
|
const HeaderSection: FC = () => {
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { activeSection, activeItemIndex, data, setActiveSection, setActiveItemIndex } = usePersonalityStore()
|
||||||
|
|
||||||
|
const handleSectionClick = (section: "header" | "content" | "footer") => {
|
||||||
|
setActiveSection(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleItemClick = (index: number) => {
|
||||||
|
setActiveItemIndex(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr onClick={() => handleSectionClick("header")}>
|
||||||
|
{
|
||||||
|
data.header.columnsCount > 0 ?
|
||||||
|
<td style={{
|
||||||
|
padding: '10px',
|
||||||
|
border: activeSection === "header" ? '1px dashed #0038FF' : '1px dashed #E5E7EB',
|
||||||
|
borderRadius: '24px'
|
||||||
|
}}>
|
||||||
|
<table width="100%" cellPadding="0" cellSpacing="0" border={0}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{
|
||||||
|
Array.from({ length: data.header.columnsCount }, (_, index) => {
|
||||||
|
const item = data.header.items[index];
|
||||||
|
const isActive = activeSection === "header" && activeItemIndex === index;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<td
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleItemClick(index);
|
||||||
|
}}
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
height: '103px',
|
||||||
|
border: isActive ? '1px dashed #0038FF' : '1px dashed #EAECF4',
|
||||||
|
backgroundColor: item?.backgroundColor || '#ffffff',
|
||||||
|
backgroundImage: item?.backgroundImage ? `url(${item.backgroundImage})` : 'none',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* محتوای آیتم */}
|
||||||
|
{item?.texts?.map(text => (
|
||||||
|
<div key={text.id} style={{ color: text.color }}>
|
||||||
|
{text.text}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{item?.buttons?.map(button => (
|
||||||
|
<button
|
||||||
|
key={button.id}
|
||||||
|
style={{
|
||||||
|
backgroundColor: button.backgroundColor,
|
||||||
|
color: button.textColor
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{button.text}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</td>
|
||||||
|
{index < data.header.columnsCount - 1 && (
|
||||||
|
<td style={{ width: '16px' }}></td>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
:
|
||||||
|
<td style={{
|
||||||
|
height: '123px',
|
||||||
|
border: activeSection === "header" ? '1px dashed #0038FF' : '1px dashed #E5E7EB',
|
||||||
|
borderRadius: '24px',
|
||||||
|
textAlign: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
padding: '10px'
|
||||||
|
}}>
|
||||||
|
<div style={{ color: '#888', fontSize: '14px', marginBottom: '8px' }}>
|
||||||
|
{t('setting.header_email')}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<AddCircle size={24} color='#888' />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HeaderSection
|
||||||
@@ -4,35 +4,75 @@ import UploadBoxDraggble from '@/components/UploadBoxDraggble';
|
|||||||
import Select from '@/components/Select';
|
import Select from '@/components/Select';
|
||||||
import { AlignBottom, AlignHorizontally, AlignLeft, AlignRight, AlignTop, AlignVertically } from 'iconsax-react';
|
import { AlignBottom, AlignHorizontally, AlignLeft, AlignRight, AlignTop, AlignVertically } from 'iconsax-react';
|
||||||
import ColorPicker from '@/components/ColorPicker';
|
import ColorPicker from '@/components/ColorPicker';
|
||||||
|
import { usePersonalityStore } from '../store/Store';
|
||||||
|
|
||||||
const SettingSideBar: FC = () => {
|
const SettingSideBar: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { activeSection, setColumnsCount, updateActiveItem } = usePersonalityStore()
|
||||||
const [color, setColor] = useState("#aabbcc");
|
const [color, setColor] = useState("#aabbcc");
|
||||||
|
|
||||||
|
const handleColumnClick = (columns: number) => {
|
||||||
|
if (activeSection) {
|
||||||
|
setColumnsCount(activeSection as "header" | "content" | "footer", columns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleColorChange = (color: string) => {
|
||||||
|
setColor(color)
|
||||||
|
updateActiveItem({ backgroundColor: color })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUploadBackground = (file: File[]) => {
|
||||||
|
if (activeSection && file.length > 0) {
|
||||||
|
const imageUrl = URL.createObjectURL(file[0])
|
||||||
|
console.log('Setting background image:', imageUrl)
|
||||||
|
updateActiveItem({ backgroundImage: imageUrl })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activeSection) {
|
||||||
|
return (
|
||||||
|
<div className='text-center text-gray-500 mt-8'>
|
||||||
|
{t('setting.select_section')}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='text-lg'>{t('setting.setting')}</div>
|
<div className='text-lg'>{t('setting.setting')}</div>
|
||||||
|
|
||||||
<div className='flex h-[51px] gap-5 mt-6'>
|
<div className='flex h-[51px] gap-5 mt-6'>
|
||||||
<div className='w-[102px] h-full bg-[#EAECF4]'></div>
|
<div
|
||||||
<div className='w-[102px] h-full flex gap-1'>
|
className='w-[102px] h-full bg-[#EAECF4] cursor-pointer hover:bg-[#d1d5db] transition-colors'
|
||||||
<div className='flex-1 bg-[#EAECF4]'></div>
|
onClick={() => handleColumnClick(1)}
|
||||||
<div className='flex-1 bg-[#EAECF4]'></div>
|
></div>
|
||||||
|
<div
|
||||||
|
className='w-[102px] h-full flex gap-1 cursor-pointer group'
|
||||||
|
onClick={() => handleColumnClick(2)}
|
||||||
|
>
|
||||||
|
<div className='flex-1 bg-[#EAECF4] group-hover:bg-[#d1d5db] transition-colors'></div>
|
||||||
|
<div className='flex-1 bg-[#EAECF4] group-hover:bg-[#d1d5db] transition-colors'></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex h-[51px] gap-5 mt-5'>
|
<div className='flex h-[51px] gap-5 mt-5'>
|
||||||
<div className='w-[102px] h-full flex gap-1'>
|
<div
|
||||||
<div className='flex-1 bg-[#EAECF4]'></div>
|
className='w-[102px] h-full flex gap-1 cursor-pointer group'
|
||||||
<div className='flex-1 bg-[#EAECF4]'></div>
|
onClick={() => handleColumnClick(3)}
|
||||||
<div className='flex-1 bg-[#EAECF4]'></div>
|
>
|
||||||
|
<div className='flex-1 bg-[#EAECF4] group-hover:bg-[#d1d5db] transition-colors'></div>
|
||||||
|
<div className='flex-1 bg-[#EAECF4] group-hover:bg-[#d1d5db] transition-colors'></div>
|
||||||
|
<div className='flex-1 bg-[#EAECF4] group-hover:bg-[#d1d5db] transition-colors'></div>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-[102px] h-full flex gap-1'>
|
<div
|
||||||
<div className='flex-1 bg-[#EAECF4]'></div>
|
className='w-[102px] h-full flex gap-1 cursor-pointer group'
|
||||||
<div className='flex-1 bg-[#EAECF4]'></div>
|
onClick={() => handleColumnClick(4)}
|
||||||
<div className='flex-1 bg-[#EAECF4]'></div>
|
>
|
||||||
<div className='flex-1 bg-[#EAECF4]'></div>
|
<div className='flex-1 bg-[#EAECF4] group-hover:bg-[#d1d5db] transition-colors'></div>
|
||||||
|
<div className='flex-1 bg-[#EAECF4] group-hover:bg-[#d1d5db] transition-colors'></div>
|
||||||
|
<div className='flex-1 bg-[#EAECF4] group-hover:bg-[#d1d5db] transition-colors'></div>
|
||||||
|
<div className='flex-1 bg-[#EAECF4] group-hover:bg-[#d1d5db] transition-colors'></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -40,14 +80,14 @@ const SettingSideBar: FC = () => {
|
|||||||
<ColorPicker
|
<ColorPicker
|
||||||
label={t('setting.background')}
|
label={t('setting.background')}
|
||||||
defaultColor={color}
|
defaultColor={color}
|
||||||
changeColor={setColor}
|
changeColor={handleColorChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-5'>
|
<div className='mt-5'>
|
||||||
<UploadBoxDraggble
|
<UploadBoxDraggble
|
||||||
label={t('setting.upload_background')}
|
label={t('setting.upload_background')}
|
||||||
onChange={() => null}
|
onChange={handleUploadBackground}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -65,20 +105,50 @@ const SettingSideBar: FC = () => {
|
|||||||
{t('setting.horizontal_position')}
|
{t('setting.horizontal_position')}
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-3 flex gap-4'>
|
<div className='mt-3 flex gap-4'>
|
||||||
<AlignRight size={20} color='#000' />
|
<AlignRight
|
||||||
<AlignVertically size={20} color='#000' />
|
size={20}
|
||||||
<AlignLeft size={20} color='#000' />
|
color='#000'
|
||||||
|
className='cursor-pointer'
|
||||||
|
onClick={() => updateActiveItem({ alignment: 'right' })}
|
||||||
|
/>
|
||||||
|
<AlignVertically
|
||||||
|
size={20}
|
||||||
|
color='#000'
|
||||||
|
className='cursor-pointer'
|
||||||
|
onClick={() => updateActiveItem({ alignment: 'center' })}
|
||||||
|
/>
|
||||||
|
<AlignLeft
|
||||||
|
size={20}
|
||||||
|
color='#000'
|
||||||
|
className='cursor-pointer'
|
||||||
|
onClick={() => updateActiveItem({ alignment: 'left' })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className='text-sm'>
|
<div className='text-sm'>
|
||||||
{t('setting.horizontal_position')}
|
{t('setting.vertical_position')}
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-3 flex gap-4'>
|
<div className='mt-3 flex gap-4'>
|
||||||
<AlignBottom size={20} color='#000' />
|
<AlignBottom
|
||||||
<AlignHorizontally size={20} color='#000' />
|
size={20}
|
||||||
<AlignTop size={20} color='#000' />
|
color='#000'
|
||||||
|
className='cursor-pointer'
|
||||||
|
onClick={() => updateActiveItem({ verticalAlignment: 'bottom' })}
|
||||||
|
/>
|
||||||
|
<AlignHorizontally
|
||||||
|
size={20}
|
||||||
|
color='#000'
|
||||||
|
className='cursor-pointer'
|
||||||
|
onClick={() => updateActiveItem({ verticalAlignment: 'middle' })}
|
||||||
|
/>
|
||||||
|
<AlignTop
|
||||||
|
size={20}
|
||||||
|
color='#000'
|
||||||
|
className='cursor-pointer'
|
||||||
|
onClick={() => updateActiveItem({ verticalAlignment: 'top' })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,294 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import {
|
||||||
|
PersonalityStore,
|
||||||
|
PersonalityDataType,
|
||||||
|
SectionItemType,
|
||||||
|
TextType,
|
||||||
|
ButtonType,
|
||||||
|
} from "../types/Types";
|
||||||
|
|
||||||
|
export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
||||||
|
data: {
|
||||||
|
header: { columnsCount: 0, items: [] },
|
||||||
|
content: { columnsCount: 0, items: [] },
|
||||||
|
footer: { columnsCount: 0, items: [] },
|
||||||
|
},
|
||||||
|
|
||||||
|
activeSection: "",
|
||||||
|
activeItemIndex: 0,
|
||||||
|
|
||||||
|
setActiveSection: (section: "header" | "content" | "footer") =>
|
||||||
|
set({ activeSection: section, activeItemIndex: 0 }),
|
||||||
|
|
||||||
|
setActiveItemIndex: (index: number) => set({ activeItemIndex: index }),
|
||||||
|
|
||||||
|
setColumnsCount: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
columnsCount: number
|
||||||
|
) =>
|
||||||
|
set((state) => {
|
||||||
|
const currentItems = state.data[sectionKey].items;
|
||||||
|
const newItems = Array.from(
|
||||||
|
{ length: columnsCount },
|
||||||
|
(_, index) =>
|
||||||
|
currentItems[index] || {
|
||||||
|
id: uuidv4(),
|
||||||
|
backgroundColor: "#ffffff",
|
||||||
|
texts: [],
|
||||||
|
buttons: [],
|
||||||
|
alignment: "center" as const,
|
||||||
|
verticalAlignment: "middle" as const,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
columnsCount,
|
||||||
|
items: newItems,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
activeItemIndex: 0,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
setItems: (sectionKey: keyof PersonalityDataType, items: SectionItemType[]) =>
|
||||||
|
set((state) => ({
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
|
items,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
|
||||||
|
addItem: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
item: Omit<SectionItemType, "id">
|
||||||
|
) =>
|
||||||
|
set((state) => ({
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
|
items: [...state.data[sectionKey].items, { ...item, id: uuidv4() }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
|
||||||
|
updateActiveItem: (updates: Partial<SectionItemType>) =>
|
||||||
|
set((state) => {
|
||||||
|
if (!state.activeSection) return state;
|
||||||
|
|
||||||
|
const sectionKey = state.activeSection as keyof PersonalityDataType;
|
||||||
|
const items = state.data[sectionKey].items;
|
||||||
|
const activeIndex = state.activeItemIndex;
|
||||||
|
|
||||||
|
if (activeIndex >= items.length) return state;
|
||||||
|
|
||||||
|
console.log("Updating active item:", {
|
||||||
|
sectionKey,
|
||||||
|
activeIndex,
|
||||||
|
updates,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedItems = items.map((item, index) =>
|
||||||
|
index === activeIndex ? { ...item, ...updates } : item
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
|
items: updatedItems,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateItem: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
id: string,
|
||||||
|
updates: Partial<SectionItemType>
|
||||||
|
) =>
|
||||||
|
set((state) => ({
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
|
items: state.data[sectionKey].items.map((item) =>
|
||||||
|
item.id === id ? { ...item, ...updates } : item
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
|
||||||
|
removeItem: (sectionKey: keyof PersonalityDataType, id: string) =>
|
||||||
|
set((state) => ({
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
|
items: state.data[sectionKey].items.filter((item) => item.id !== id),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
|
||||||
|
addText: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
itemId: string,
|
||||||
|
text: Omit<TextType, "id">
|
||||||
|
) =>
|
||||||
|
set((state) => ({
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
|
items: state.data[sectionKey].items.map((item) =>
|
||||||
|
item.id === itemId
|
||||||
|
? {
|
||||||
|
...item,
|
||||||
|
texts: [...item.texts, { ...text, id: uuidv4() }],
|
||||||
|
}
|
||||||
|
: item
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
|
||||||
|
addTextToActiveItem: (text: Omit<TextType, "id">) =>
|
||||||
|
set((state) => {
|
||||||
|
if (!state.activeSection) return state;
|
||||||
|
|
||||||
|
const sectionKey = state.activeSection as keyof PersonalityDataType;
|
||||||
|
const items = state.data[sectionKey].items;
|
||||||
|
const activeIndex = state.activeItemIndex;
|
||||||
|
|
||||||
|
if (activeIndex >= items.length) return state;
|
||||||
|
|
||||||
|
const updatedItems = items.map((item, index) =>
|
||||||
|
index === activeIndex
|
||||||
|
? {
|
||||||
|
...item,
|
||||||
|
texts: [...item.texts, { ...text, id: uuidv4() }],
|
||||||
|
}
|
||||||
|
: item
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
|
items: updatedItems,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
addButton: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
itemId: string,
|
||||||
|
button: Omit<ButtonType, "id">
|
||||||
|
) =>
|
||||||
|
set((state) => ({
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
|
items: state.data[sectionKey].items.map((item) =>
|
||||||
|
item.id === itemId
|
||||||
|
? {
|
||||||
|
...item,
|
||||||
|
buttons: [...item.buttons, { ...button, id: uuidv4() }],
|
||||||
|
}
|
||||||
|
: item
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
|
||||||
|
addButtonToActiveItem: (button: Omit<ButtonType, "id">) =>
|
||||||
|
set((state) => {
|
||||||
|
if (!state.activeSection) return state;
|
||||||
|
|
||||||
|
const sectionKey = state.activeSection as keyof PersonalityDataType;
|
||||||
|
const items = state.data[sectionKey].items;
|
||||||
|
const activeIndex = state.activeItemIndex;
|
||||||
|
|
||||||
|
if (activeIndex >= items.length) return state;
|
||||||
|
|
||||||
|
const updatedItems = items.map((item, index) =>
|
||||||
|
index === activeIndex
|
||||||
|
? {
|
||||||
|
...item,
|
||||||
|
buttons: [...item.buttons, { ...button, id: uuidv4() }],
|
||||||
|
}
|
||||||
|
: item
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
|
items: updatedItems,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateText: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
itemId: string,
|
||||||
|
textId: string,
|
||||||
|
updates: Partial<TextType>
|
||||||
|
) =>
|
||||||
|
set((state) => ({
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
|
items: state.data[sectionKey].items.map((item) =>
|
||||||
|
item.id === itemId
|
||||||
|
? {
|
||||||
|
...item,
|
||||||
|
texts: item.texts.map((text) =>
|
||||||
|
text.id === textId ? { ...text, ...updates } : text
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: item
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
|
||||||
|
updateButton: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
itemId: string,
|
||||||
|
buttonId: string,
|
||||||
|
updates: Partial<ButtonType>
|
||||||
|
) =>
|
||||||
|
set((state) => ({
|
||||||
|
data: {
|
||||||
|
...state.data,
|
||||||
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
|
items: state.data[sectionKey].items.map((item) =>
|
||||||
|
item.id === itemId
|
||||||
|
? {
|
||||||
|
...item,
|
||||||
|
buttons: item.buttons.map((button) =>
|
||||||
|
button.id === buttonId ? { ...button, ...updates } : button
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: item
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
export type PersonalityDataType = {
|
||||||
|
header: SectionType;
|
||||||
|
content: SectionType;
|
||||||
|
footer: SectionType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SectionType = {
|
||||||
|
columnsCount: number;
|
||||||
|
items: SectionItemType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SectionItemType = {
|
||||||
|
id: string;
|
||||||
|
backgroundColor: string;
|
||||||
|
backgroundImage?: string;
|
||||||
|
texts: TextType[];
|
||||||
|
buttons: ButtonType[];
|
||||||
|
alignment?: "left" | "center" | "right";
|
||||||
|
verticalAlignment?: "top" | "middle" | "bottom";
|
||||||
|
style?: {
|
||||||
|
borderRadius?: number;
|
||||||
|
padding?: string;
|
||||||
|
fontFamily?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TextType = {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
fontSize?: number;
|
||||||
|
fontFamily?: string;
|
||||||
|
color?: string;
|
||||||
|
alignment?: "left" | "center" | "right";
|
||||||
|
verticalAlignment?: "top" | "middle" | "bottom";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ButtonType = {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
link: string;
|
||||||
|
size: "small" | "medium" | "large";
|
||||||
|
isBorder: boolean;
|
||||||
|
borderSize: number;
|
||||||
|
textColor: string;
|
||||||
|
backgroundColor: string;
|
||||||
|
borderColor: string;
|
||||||
|
alignment?: "left" | "center" | "right";
|
||||||
|
verticalAlignment?: "top" | "middle" | "bottom";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PersonalityStore = {
|
||||||
|
data: PersonalityDataType;
|
||||||
|
activeSection: "header" | "content" | "footer" | "";
|
||||||
|
activeItemIndex: number;
|
||||||
|
|
||||||
|
setActiveSection: (section: "header" | "content" | "footer") => void;
|
||||||
|
setActiveItemIndex: (index: number) => void;
|
||||||
|
|
||||||
|
setItems: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
items: SectionItemType[]
|
||||||
|
) => void;
|
||||||
|
addItem: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
item: Omit<SectionItemType, "id">
|
||||||
|
) => void;
|
||||||
|
updateItem: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
id: string,
|
||||||
|
updates: Partial<SectionItemType>
|
||||||
|
) => void;
|
||||||
|
updateActiveItem: (updates: Partial<SectionItemType>) => void;
|
||||||
|
removeItem: (sectionKey: keyof PersonalityDataType, id: string) => void;
|
||||||
|
setColumnsCount: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
columnsCount: number
|
||||||
|
) => void;
|
||||||
|
addText: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
itemId: string,
|
||||||
|
text: Omit<TextType, "id">
|
||||||
|
) => void;
|
||||||
|
addTextToActiveItem: (text: Omit<TextType, "id">) => void;
|
||||||
|
addButton: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
itemId: string,
|
||||||
|
button: Omit<ButtonType, "id">
|
||||||
|
) => void;
|
||||||
|
addButtonToActiveItem: (button: Omit<ButtonType, "id">) => void;
|
||||||
|
updateText: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
itemId: string,
|
||||||
|
textId: string,
|
||||||
|
updates: Partial<TextType>
|
||||||
|
) => void;
|
||||||
|
updateButton: (
|
||||||
|
sectionKey: keyof PersonalityDataType,
|
||||||
|
itemId: string,
|
||||||
|
buttonId: string,
|
||||||
|
updates: Partial<ButtonType>
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user