417 lines
16 KiB
TypeScript
417 lines
16 KiB
TypeScript
import Button from '@/components/Button'
|
|
import ColorPicker from '@/components/ColorPicker'
|
|
import Input from '@/components/Input'
|
|
import Select from '@/components/Select'
|
|
import { AlignRight, AlignBottom, AlignHorizontally, AlignLeft, AlignTop, AlignVertically, Add, Edit, Trash, Link2 } from 'iconsax-react'
|
|
import React, { FC, useState, useEffect } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { usePersonalityStore } from '../store/Store'
|
|
import { SocialNetworkType, SocialIconSize, SocialIconStyle, HorizontalAlignment, VerticalAlignment, ElementType } from '../types/Types'
|
|
import { getSocialIconPath, hasPngIcon, getSocialSvgIcon } from '../utils/socialIcons'
|
|
|
|
const SocialSidebar: FC = () => {
|
|
|
|
const { t } = useTranslation()
|
|
const {
|
|
addSocialToActiveItem,
|
|
selectedElement,
|
|
setSelectedElement,
|
|
data,
|
|
updateSocial,
|
|
deleteSocial
|
|
} = usePersonalityStore()
|
|
|
|
const [networkType, setNetworkType] = useState<SocialNetworkType>(SocialNetworkType.INSTAGRAM)
|
|
const [link, setLink] = useState<string>('')
|
|
const [size, setSize] = useState<SocialIconSize>(SocialIconSize.MEDIUM)
|
|
const [style, setStyle] = useState<SocialIconStyle>(SocialIconStyle.CIRCLE)
|
|
const [color, setColor] = useState<string>('#000000')
|
|
const [horizontalAlignment, setHorizontalAlignment] = useState<HorizontalAlignment>(HorizontalAlignment.CENTER)
|
|
const [verticalAlignment, setVerticalAlignment] = useState<VerticalAlignment>(VerticalAlignment.MIDDLE)
|
|
|
|
const isEditMode = selectedElement?.type === ElementType.SOCIAL;
|
|
|
|
// Load selected social data when element is selected
|
|
useEffect(() => {
|
|
if (isEditMode && selectedElement) {
|
|
try {
|
|
const sectionData = data[selectedElement.sectionKey];
|
|
|
|
if (!sectionData || !sectionData.items) {
|
|
console.error('Section data or items not found');
|
|
return;
|
|
}
|
|
|
|
const item = sectionData.items.find(item => item.id === selectedElement.itemId);
|
|
|
|
if (!item) {
|
|
console.error('Item not found');
|
|
return;
|
|
}
|
|
|
|
const social = item.socials?.find(s => s.id === selectedElement.elementId);
|
|
|
|
if (social) {
|
|
setNetworkType(social.networkType);
|
|
setLink(social.link);
|
|
setSize(social.size);
|
|
setStyle(social.style);
|
|
setColor(social.color || '#000000');
|
|
setHorizontalAlignment(social.alignment || HorizontalAlignment.CENTER);
|
|
setVerticalAlignment(social.verticalAlignment || VerticalAlignment.MIDDLE);
|
|
} else {
|
|
console.error('Social element not found');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading social data:', error);
|
|
}
|
|
} else {
|
|
// Reset form when not in edit mode
|
|
setNetworkType(SocialNetworkType.INSTAGRAM)
|
|
setLink('')
|
|
setSize(SocialIconSize.MEDIUM)
|
|
setStyle(SocialIconStyle.CIRCLE)
|
|
setColor('#000000')
|
|
setHorizontalAlignment(HorizontalAlignment.CENTER)
|
|
setVerticalAlignment(VerticalAlignment.MIDDLE)
|
|
}
|
|
}, [selectedElement, isEditMode, data]);
|
|
|
|
const networkOptions = [
|
|
{ label: 'Instagram', value: SocialNetworkType.INSTAGRAM },
|
|
{ label: 'Facebook', value: SocialNetworkType.FACEBOOK },
|
|
{ label: 'Twitter', value: SocialNetworkType.TWITTER },
|
|
{ label: 'LinkedIn', value: SocialNetworkType.LINKEDIN },
|
|
{ label: 'YouTube', value: SocialNetworkType.YOUTUBE },
|
|
{ label: 'Telegram', value: SocialNetworkType.TELEGRAM },
|
|
{ label: 'WhatsApp', value: SocialNetworkType.WHATSAPP },
|
|
{ label: 'TikTok', value: SocialNetworkType.TIKTOK },
|
|
{ label: 'Pinterest', value: SocialNetworkType.PINTEREST },
|
|
{ label: 'Snapchat', value: SocialNetworkType.SNAPCHAT },
|
|
{ label: 'GitHub', value: SocialNetworkType.GITHUB },
|
|
{ label: 'Discord', value: SocialNetworkType.DISCORD },
|
|
]
|
|
|
|
const sizeOptions = [
|
|
{ label: 'کوچک', value: SocialIconSize.SMALL },
|
|
{ label: 'متوسط', value: SocialIconSize.MEDIUM },
|
|
{ label: 'بزرگ', value: SocialIconSize.LARGE }
|
|
]
|
|
|
|
const styleOptions = [
|
|
{ label: 'دایره', value: SocialIconStyle.CIRCLE },
|
|
{ label: 'مربع', value: SocialIconStyle.SQUARE },
|
|
{ label: 'گرد', value: SocialIconStyle.ROUNDED }
|
|
]
|
|
|
|
const handleAddSocial = () => {
|
|
if (link.trim()) {
|
|
addSocialToActiveItem({
|
|
networkType,
|
|
link,
|
|
size,
|
|
style,
|
|
color,
|
|
alignment: horizontalAlignment,
|
|
verticalAlignment,
|
|
})
|
|
|
|
// Reset form after adding
|
|
setNetworkType(SocialNetworkType.INSTAGRAM)
|
|
setLink('')
|
|
setSize(SocialIconSize.MEDIUM)
|
|
setStyle(SocialIconStyle.CIRCLE)
|
|
setColor('#000000')
|
|
setHorizontalAlignment(HorizontalAlignment.CENTER)
|
|
setVerticalAlignment(VerticalAlignment.MIDDLE)
|
|
}
|
|
}
|
|
|
|
const handleUpdateSocial = () => {
|
|
if (selectedElement && link.trim()) {
|
|
updateSocial(
|
|
selectedElement.sectionKey,
|
|
selectedElement.itemId,
|
|
selectedElement.elementId,
|
|
{
|
|
networkType,
|
|
link,
|
|
size,
|
|
style,
|
|
color,
|
|
alignment: horizontalAlignment,
|
|
verticalAlignment,
|
|
}
|
|
);
|
|
|
|
// Exit edit mode
|
|
setSelectedElement(null);
|
|
}
|
|
}
|
|
|
|
const handleDeleteSocial = () => {
|
|
if (selectedElement) {
|
|
deleteSocial(
|
|
selectedElement.sectionKey,
|
|
selectedElement.itemId,
|
|
selectedElement.elementId
|
|
);
|
|
|
|
// Exit edit mode
|
|
setSelectedElement(null);
|
|
}
|
|
}
|
|
|
|
const handleCancelEdit = () => {
|
|
setSelectedElement(null);
|
|
}
|
|
|
|
// Preview component for selected social network
|
|
const renderPreview = () => {
|
|
const getIconSize = () => {
|
|
switch (size) {
|
|
case SocialIconSize.SMALL:
|
|
return { width: '24px', height: '24px' }
|
|
case SocialIconSize.MEDIUM:
|
|
return { width: '32px', height: '32px' }
|
|
case SocialIconSize.LARGE:
|
|
return { width: '40px', height: '40px' }
|
|
default:
|
|
return { width: '32px', height: '32px' }
|
|
}
|
|
}
|
|
|
|
const getIconStyle = () => {
|
|
const iconSize = getIconSize()
|
|
const baseStyle = {
|
|
...iconSize,
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
color: color,
|
|
transition: 'all 0.2s ease',
|
|
}
|
|
|
|
switch (style) {
|
|
case SocialIconStyle.CIRCLE:
|
|
return {
|
|
...baseStyle,
|
|
borderRadius: '50%',
|
|
}
|
|
case SocialIconStyle.SQUARE:
|
|
return {
|
|
...baseStyle,
|
|
borderRadius: '0',
|
|
}
|
|
case SocialIconStyle.ROUNDED:
|
|
return {
|
|
...baseStyle,
|
|
borderRadius: '8px',
|
|
}
|
|
default:
|
|
return baseStyle
|
|
}
|
|
}
|
|
|
|
const renderIcon = () => {
|
|
if (hasPngIcon(networkType)) {
|
|
const iconPath = getSocialIconPath(networkType)
|
|
return (
|
|
<img
|
|
src={iconPath}
|
|
alt={`${networkType} icon`}
|
|
style={{
|
|
width: '100%',
|
|
height: '100%',
|
|
objectFit: 'contain',
|
|
display: 'block'
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
// Use shared SVG function for fallback
|
|
return getSocialSvgIcon(networkType, true) as React.ReactElement
|
|
}
|
|
|
|
return (
|
|
<div className='mt-4 p-4 bg-gray-50 rounded-lg'>
|
|
<div className='text-sm text-gray-600 mb-2'>{t('setting.preview')}</div>
|
|
<div className='flex justify-center'>
|
|
<div style={getIconStyle()}>
|
|
{renderIcon()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div className='text-lg flex items-center justify-between'>
|
|
{t('setting.social_networks')}
|
|
{isEditMode && (
|
|
<div className='flex items-center gap-2'>
|
|
<span className='text-sm text-blue-600'>{t('setting.edit_mode')}</span>
|
|
<Edit size={16} color='#0038FF' />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className='mt-8'>
|
|
<Select
|
|
label={t('setting.social_network')}
|
|
items={networkOptions}
|
|
value={networkType}
|
|
onChange={(e) => setNetworkType(e.target.value as SocialNetworkType)}
|
|
placeholder={t('setting.select_network')}
|
|
/>
|
|
</div>
|
|
|
|
{/* Preview of selected social network */}
|
|
{renderPreview()}
|
|
|
|
<div className='mt-5'>
|
|
<Input
|
|
label={t('setting.social_link')}
|
|
value={link}
|
|
onChange={(e) => setLink(e.target.value)}
|
|
endIcon={<Link2 size={18} color='#888' />}
|
|
placeholder="https://..."
|
|
/>
|
|
</div>
|
|
|
|
<div className='mt-5'>
|
|
<Select
|
|
label={t('setting.size')}
|
|
items={sizeOptions}
|
|
value={size}
|
|
onChange={(e) => setSize(e.target.value as SocialIconSize)}
|
|
placeholder={t('setting.select')}
|
|
/>
|
|
</div>
|
|
|
|
<div className='mt-5'>
|
|
<Select
|
|
label={t('setting.icon_style')}
|
|
items={styleOptions}
|
|
value={style}
|
|
onChange={(e) => setStyle(e.target.value as SocialIconStyle)}
|
|
placeholder={t('setting.select')}
|
|
/>
|
|
</div>
|
|
|
|
<div className='mt-5'>
|
|
<ColorPicker
|
|
label={t('setting.icon_color')}
|
|
defaultColor={color}
|
|
changeColor={setColor}
|
|
/>
|
|
</div>
|
|
|
|
|
|
|
|
<div className='mt-5 flex justify-between'>
|
|
<div>
|
|
<div className='text-sm'>
|
|
{t('setting.horizontal_position')}
|
|
</div>
|
|
<div className='mt-3 flex gap-4'>
|
|
<div
|
|
onClick={() => setHorizontalAlignment(HorizontalAlignment.RIGHT)}
|
|
className='cursor-pointer'
|
|
>
|
|
<AlignRight size={20} color={horizontalAlignment === HorizontalAlignment.RIGHT ? '#0038FF' : '#000'} />
|
|
</div>
|
|
<div
|
|
onClick={() => setHorizontalAlignment(HorizontalAlignment.CENTER)}
|
|
className='cursor-pointer'
|
|
>
|
|
<AlignVertically size={20} color={horizontalAlignment === HorizontalAlignment.CENTER ? '#0038FF' : '#000'} />
|
|
</div>
|
|
<div
|
|
onClick={() => setHorizontalAlignment(HorizontalAlignment.LEFT)}
|
|
className='cursor-pointer'
|
|
>
|
|
<AlignLeft size={20} color={horizontalAlignment === HorizontalAlignment.LEFT ? '#0038FF' : '#000'} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div className='text-sm'>
|
|
{t('setting.vertical_position')}
|
|
</div>
|
|
<div className='mt-3 flex gap-4'>
|
|
<div
|
|
onClick={() => setVerticalAlignment(VerticalAlignment.TOP)}
|
|
className='cursor-pointer'
|
|
>
|
|
<AlignTop size={20} color={verticalAlignment === VerticalAlignment.TOP ? '#0038FF' : '#000'} />
|
|
</div>
|
|
<div
|
|
onClick={() => setVerticalAlignment(VerticalAlignment.MIDDLE)}
|
|
className='cursor-pointer'
|
|
>
|
|
<AlignHorizontally size={20} color={verticalAlignment === VerticalAlignment.MIDDLE ? '#0038FF' : '#000'} />
|
|
</div>
|
|
<div
|
|
onClick={() => setVerticalAlignment(VerticalAlignment.BOTTOM)}
|
|
className='cursor-pointer'
|
|
>
|
|
<AlignBottom size={20} color={verticalAlignment === VerticalAlignment.BOTTOM ? '#0038FF' : '#000'} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className='mt-7'>
|
|
{isEditMode ? (
|
|
<div className='space-y-3'>
|
|
<Button
|
|
onClick={handleUpdateSocial}
|
|
className='bg-[#0038FF] text-white w-full'
|
|
disabled={!link.trim()}
|
|
>
|
|
<div className='flex justify-center items-center gap-2'>
|
|
<Edit size={18} color='white' />
|
|
<span>{t('setting.update_social')}</span>
|
|
</div>
|
|
</Button>
|
|
<div className='flex gap-2'>
|
|
<Button
|
|
onClick={handleDeleteSocial}
|
|
className='bg-red-500 text-white flex-1'
|
|
>
|
|
<div className='flex justify-center items-center gap-1'>
|
|
<Trash size={16} color='white' />
|
|
<span className='text-sm'>{t('setting.delete')}</span>
|
|
</div>
|
|
</Button>
|
|
<Button
|
|
onClick={handleCancelEdit}
|
|
className='bg-gray-500 text-white flex-1'
|
|
>
|
|
<span className='text-sm'>{t('setting.cancel')}</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<Button
|
|
onClick={handleAddSocial}
|
|
className='bg-[#ECEEF5] text-black w-full'
|
|
disabled={!link.trim()}
|
|
>
|
|
<div className='flex justify-center items-center gap-2'>
|
|
<Add size={24} color='black' />
|
|
<span>{t('setting.add_social')}</span>
|
|
</div>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default SocialSidebar
|