import React, { useEffect, useState } from 'react' import { Helmet } from 'react-helmet' import navigationIcon from '@/proxy/menus/navigation-icon.config' import { AboutDto } from '@/proxy/about/models' import { getAbout, saveAboutPage } from '@/services/about' import { useLocalization } from '@/utils/hooks/useLocalization' import Loading from '@/components/shared/Loading' import { APP_NAME } from '@/constants/app.constant' import { Notification, toast } from '@/components/ui' import { useStoreState } from '@/store' import { useStoreActions } from '@/store' import DesignerDrawer from './designer/DesignerDrawer' import SelectableBlock from './designer/SelectableBlock' import { DesignerSelection } from './designer/types' import { useDesignerState } from './designer/useDesignerState' interface AboutStatContent { icon: string value: string label: string labelKey: string styleClassKey: string styleClass: string valueStyleClassKey: string valueStyleClass: string labelStyleClassKey: string labelStyleClass: string useCounter?: boolean counterEnd?: string counterSuffix?: string counterDuration?: number } interface AboutDescriptionContent { key: string text: string styleClassKey: string styleClass: string } interface AboutSectionContent { title: string description: string titleKey: string descriptionKey: string cardStyleClassKey: string cardStyleClass: string titleStyleClassKey: string titleStyleClass: string descriptionStyleClassKey: string descriptionStyleClass: string } interface AboutContent { heroTitle: string heroTitleKey: string heroTitleStyleClassKey: string heroTitleStyleClass: string heroSubtitle: string heroSubtitleKey: string heroSubtitleStyleClassKey: string heroSubtitleStyleClass: string heroImage: string heroImageKey: string heroSectionStyleClassKey: string heroSectionStyleClass: string descriptionsContainerStyleClassKey: string descriptionsContainerStyleClass: string stats: AboutStatContent[] descriptions: AboutDescriptionContent[] sections: AboutSectionContent[] } const ABOUT_HERO_IMAGE = 'https://images.pexels.com/photos/3183183/pexels-photo-3183183.jpeg?auto=compress&cs=tinysrgb&w=1920' const ABOUT_HERO_TITLE_KEY = 'App.About' const ABOUT_HERO_SUBTITLE_KEY = 'Public.about.subtitle' const ABOUT_HERO_IMAGE_KEY = 'Public.about.heroImage' const ABOUT_HERO_SECTION_STYLE_KEY = 'Public.about.hero.sectionStyleClass' const ABOUT_HERO_TITLE_STYLE_KEY = 'Public.about.hero.titleStyleClass' const ABOUT_HERO_SUBTITLE_STYLE_KEY = 'Public.about.hero.subtitleStyleClass' const ABOUT_DESCRIPTIONS_CONTAINER_STYLE_KEY = 'Public.about.descriptions.containerStyleClass' function isLikelyLocalizationKey(value?: string) { return Boolean(value && /^[A-Za-z0-9_.-]+$/.test(value) && value.includes('.')) } function resolveLocalizedValue( translate: (key: string) => string, keyOrValue: string | undefined, fallback = '', ) { if (!keyOrValue) { return fallback } if (!isLikelyLocalizationKey(keyOrValue)) { return keyOrValue } const translatedValue = translate('::' + keyOrValue) return translatedValue === keyOrValue ? fallback || keyOrValue : translatedValue } function buildAboutContent( about: AboutDto | undefined, translate: (key: string) => string, ): AboutContent { return { heroTitle: resolveLocalizedValue(translate, ABOUT_HERO_TITLE_KEY, 'About'), heroTitleKey: ABOUT_HERO_TITLE_KEY, heroTitleStyleClassKey: ABOUT_HERO_TITLE_STYLE_KEY, heroTitleStyleClass: resolveLocalizedValue( translate, ABOUT_HERO_TITLE_STYLE_KEY, 'text-5xl font-bold ml-4 mt-3 mb-2 text-white', ), heroSubtitle: resolveLocalizedValue(translate, ABOUT_HERO_SUBTITLE_KEY), heroSubtitleKey: ABOUT_HERO_SUBTITLE_KEY, heroSubtitleStyleClassKey: ABOUT_HERO_SUBTITLE_STYLE_KEY, heroSubtitleStyleClass: resolveLocalizedValue( translate, ABOUT_HERO_SUBTITLE_STYLE_KEY, 'text-xl max-w-3xl ml-4', ), heroImage: resolveLocalizedValue(translate, ABOUT_HERO_IMAGE_KEY, ABOUT_HERO_IMAGE), heroImageKey: ABOUT_HERO_IMAGE_KEY, heroSectionStyleClassKey: ABOUT_HERO_SECTION_STYLE_KEY, heroSectionStyleClass: resolveLocalizedValue( translate, ABOUT_HERO_SECTION_STYLE_KEY, 'relative bg-blue-900 text-white py-12', ), descriptionsContainerStyleClassKey: ABOUT_DESCRIPTIONS_CONTAINER_STYLE_KEY, descriptionsContainerStyleClass: resolveLocalizedValue( translate, ABOUT_DESCRIPTIONS_CONTAINER_STYLE_KEY, 'p-5 mx-auto text-gray-800 text-lg leading-relaxed shadow-md bg-white border-l-4 border-blue-600', ), stats: about?.statsDto.map((stat, index) => ({ styleClassKey: `Public.about.dynamic.stat.${index + 1}.styleClass`, styleClass: resolveLocalizedValue( translate, `Public.about.dynamic.stat.${index + 1}.styleClass`, 'text-center rounded-xl px-4 py-6', ), valueStyleClassKey: `Public.about.dynamic.stat.${index + 1}.valueStyleClass`, valueStyleClass: resolveLocalizedValue( translate, `Public.about.dynamic.stat.${index + 1}.valueStyleClass`, 'text-4xl font-bold text-gray-900 mb-2', ), labelStyleClassKey: `Public.about.dynamic.stat.${index + 1}.labelStyleClass`, labelStyleClass: resolveLocalizedValue( translate, `Public.about.dynamic.stat.${index + 1}.labelStyleClass`, 'text-gray-600', ), icon: stat.icon || '', value: stat.value, label: resolveLocalizedValue(translate, stat.labelKey, stat.labelKey), labelKey: (isLikelyLocalizationKey(stat.labelKey) ? stat.labelKey : undefined) || `Public.about.dynamic.stat.${index + 1}.label`, useCounter: stat.useCounter, counterEnd: stat.counterEnd, counterSuffix: stat.counterSuffix, counterDuration: stat.counterDuration, })) ?? [], descriptions: about?.descriptionsDto.map((item, index) => ({ key: (isLikelyLocalizationKey(item) ? item : undefined) || `Public.about.dynamic.description.${index + 1}`, text: resolveLocalizedValue(translate, item, item), styleClassKey: `Public.about.dynamic.description.${index + 1}.styleClass`, styleClass: resolveLocalizedValue( translate, `Public.about.dynamic.description.${index + 1}.styleClass`, index % 2 === 0 ? '' : 'text-center p-5 text-blue-800', ), })) ?? [], sections: about?.sectionsDto.map((section) => ({ title: resolveLocalizedValue(translate, section.key, section.key), description: resolveLocalizedValue(translate, section.descKey, section.descKey), titleKey: (isLikelyLocalizationKey(section.key) ? section.key : undefined) || `Public.about.dynamic.section.${section.key}.title`, descriptionKey: (isLikelyLocalizationKey(section.descKey) ? section.descKey : undefined) || `Public.about.dynamic.section.${section.key}.description`, cardStyleClassKey: `Public.about.dynamic.section.${section.key}.cardStyleClass`, cardStyleClass: resolveLocalizedValue( translate, `Public.about.dynamic.section.${section.key}.cardStyleClass`, 'bg-white p-8 rounded-xl shadow-lg', ), titleStyleClassKey: `Public.about.dynamic.section.${section.key}.titleStyleClass`, titleStyleClass: resolveLocalizedValue( translate, `Public.about.dynamic.section.${section.key}.titleStyleClass`, 'text-2xl font-bold text-gray-900 mb-4', ), descriptionStyleClassKey: `Public.about.dynamic.section.${section.key}.descriptionStyleClass`, descriptionStyleClass: resolveLocalizedValue( translate, `Public.about.dynamic.section.${section.key}.descriptionStyleClass`, 'text-gray-700', ), })) ?? [], } } const About: React.FC = () => { const { translate } = useLocalization() const { setLang } = useStoreActions((actions) => actions.locale) const { getConfig } = useStoreActions((actions) => actions.abpConfig) const configCultureName = useStoreState( (state) => state.abpConfig.config?.localization.currentCulture.cultureName, ) const localeCurrentLang = useStoreState((state) => state.locale?.currentLang) const currentLanguage = configCultureName || localeCurrentLang || 'tr' const abpLanguages = useStoreState((state) => state.abpConfig.config?.localization.languages) || [] const languageOptions = abpLanguages .filter((language) => Boolean(language.cultureName)) .map((language) => { const cultureName = language.cultureName || 'tr' return { key: cultureName.toLowerCase().split('-')[0], cultureName, displayName: language.displayName || cultureName, } }) const languagesFromConfig = languageOptions.map((language) => language.key) const editorLanguages = Array.from( new Set((languagesFromConfig.length > 0 ? languagesFromConfig : [currentLanguage]).filter(Boolean)), ) const [loading, setLoading] = useState(true) const [isSaving, setIsSaving] = useState(false) const [isPanelVisible, setIsPanelVisible] = useState(true) const [about, setAbout] = useState() const iconColors = [ 'text-blue-600', 'text-red-600', 'text-green-600', 'text-purple-600', 'text-yellow-600', 'text-indigo-600', ] function getIconColor(index: number) { return iconColors[index % iconColors.length] } const initialContent = !loading ? buildAboutContent(about, translate) : null const { content, isDesignMode, selectedBlockId, selectedLanguage, supportedLanguages, setContent, setSelectedBlockId, resetContent, } = useDesignerState('about', initialContent, { currentLanguage, supportedLanguages: editorLanguages, }) useEffect(() => { setLoading(true) const fetchServices = async () => { try { const result = await getAbout() setAbout(result.data) } catch (error) { console.error('About alınırken hata oluştu:', error) } finally { setLoading(false) } } fetchServices() }, []) const updateContent = (updater: (current: AboutContent) => AboutContent) => { setContent((current) => { if (!current) { return current } return updater(current) }) } const handleFieldChange = (fieldKey: string, value: string | string[]) => { updateContent((current) => { if ( fieldKey === 'heroTitle' || fieldKey === 'heroSubtitle' || fieldKey === 'heroImage' || fieldKey === 'heroSectionStyleClass' || fieldKey === 'heroTitleStyleClass' || fieldKey === 'heroSubtitleStyleClass' || fieldKey === 'descriptionsContainerStyleClass' ) { return { ...current, [fieldKey]: value as string, } } if (fieldKey.startsWith('description-')) { const index = Number(fieldKey.replace('description-', '')) const descriptions = [...current.descriptions] descriptions[index] = { ...descriptions[index], text: value as string, } return { ...current, descriptions, } } if (fieldKey.startsWith('descriptionStyle-')) { const index = Number(fieldKey.replace('descriptionStyle-', '')) const descriptions = [...current.descriptions] descriptions[index] = { ...descriptions[index], styleClass: value as string, } return { ...current, descriptions, } } if (selectedBlockId?.startsWith('stat-')) { const index = Number(selectedBlockId.replace('stat-', '')) const stats = [...current.stats] stats[index] = { ...stats[index], [fieldKey]: value as string, } return { ...current, stats, } } if (selectedBlockId?.startsWith('section-')) { const index = Number(selectedBlockId.replace('section-', '')) const sections = [...current.sections] sections[index] = { ...sections[index], [fieldKey]: value as string, } return { ...current, sections, } } return current }) } const selectedSelection: DesignerSelection | null = React.useMemo(() => { if (!content || !selectedBlockId) { return null } if (selectedBlockId === 'hero') { return { id: 'hero', title: content.heroTitleKey, description: 'Baslik, alt baslik ve arka plan gorselini guncelleyin.', fields: [ { key: 'heroTitle', label: content.heroTitleKey, type: 'text', value: content.heroTitle, }, { key: 'heroTitleStyleClass', label: content.heroTitleStyleClassKey, type: 'text', value: content.heroTitleStyleClass, }, { key: 'heroSubtitle', label: content.heroSubtitleKey, type: 'textarea', value: content.heroSubtitle, }, { key: 'heroSubtitleStyleClass', label: content.heroSubtitleStyleClassKey, type: 'text', value: content.heroSubtitleStyleClass, }, { key: 'heroImage', label: content.heroImageKey, type: 'image', value: content.heroImage, }, { key: 'heroSectionStyleClass', label: content.heroSectionStyleClassKey, type: 'text', value: content.heroSectionStyleClass, }, ], } } if (selectedBlockId === 'descriptions') { return { id: 'descriptions', title: 'Public.about.description.*', description: 'Orta bolumdeki aciklama metinlerini duzenleyin.', fields: [ { key: 'descriptionsContainerStyleClass', label: content.descriptionsContainerStyleClassKey, type: 'text', value: content.descriptionsContainerStyleClass, }, ...content.descriptions.flatMap((item, index) => [ { key: `description-${index}`, label: item.key || `Public.about.dynamic.description.${index + 1}`, type: 'textarea' as const, value: item.text, rows: index % 2 === 0 ? 4 : 3, }, { key: `descriptionStyle-${index}`, label: item.styleClassKey, type: 'text' as const, value: item.styleClass, }, ]), ], } } if (selectedBlockId.startsWith('stat-')) { const index = Number(selectedBlockId.replace('stat-', '')) const stat = content.stats[index] if (!stat) { return null } return { id: selectedBlockId, title: stat.labelKey, description: translate('::Public.designer.desc1'), fields: [ { key: 'icon', label: translate('::Public.designer.ikonAnahtari'), type: 'icon', value: stat.icon, placeholder: 'Ornek: FaUsers', }, { key: 'value', label: translate('::App.Listform.ListformField.Value'), type: 'text', value: stat.value }, { key: 'label', label: translate('::' + stat.labelKey), type: 'text', value: stat.label, }, { key: 'styleClass', label: stat.styleClassKey, type: 'text', value: stat.styleClass, }, { key: 'valueStyleClass', label: stat.valueStyleClassKey, type: 'text', value: stat.valueStyleClass, }, { key: 'labelStyleClass', label: stat.labelStyleClassKey, type: 'text', value: stat.labelStyleClass, }, ], } } if (selectedBlockId.startsWith('section-')) { const index = Number(selectedBlockId.replace('section-', '')) const section = content.sections[index] if (!section) { return null } return { id: selectedBlockId, title: section.titleKey, description: 'Kart basligi ve aciklama metnini duzenleyin.', fields: [ { key: 'title', label: section.titleKey, type: 'text', value: section.title, }, { key: 'description', label: section.descriptionKey, type: 'textarea', value: section.description, }, { key: 'cardStyleClass', label: section.cardStyleClassKey, type: 'text', value: section.cardStyleClass, }, { key: 'titleStyleClass', label: section.titleStyleClassKey, type: 'text', value: section.titleStyleClass, }, { key: 'descriptionStyleClass', label: section.descriptionStyleClassKey, type: 'text', value: section.descriptionStyleClass, }, ], } } return null }, [content, selectedBlockId]) const handleSaveAndExit = async () => { if (!content || isSaving) { return } setIsSaving(true) try { await saveAboutPage({ cultureName: selectedLanguage, heroTitleKey: content.heroTitleKey, heroTitleValue: content.heroTitle, heroSubtitleKey: content.heroSubtitleKey, heroSubtitleValue: content.heroSubtitle, heroImageKey: content.heroImageKey, heroImageValue: content.heroImage, stats: content.stats.map((stat, index) => ({ icon: stat.icon, value: stat.value, labelKey: stat.labelKey || `Public.about.dynamic.stat.${index + 1}.label`, labelValue: stat.label, useCounter: stat.useCounter, counterEnd: stat.counterEnd, counterSuffix: stat.counterSuffix, counterDuration: stat.counterDuration, })), descriptions: content.descriptions.map((item, index) => ({ key: item.key || `Public.about.dynamic.description.${index + 1}`, value: item.text, })), sections: content.sections.map((section, index) => ({ titleKey: section.titleKey || `Public.about.dynamic.section.${index + 1}.title`, titleValue: section.title, descriptionKey: section.descriptionKey || `Public.about.dynamic.section.${index + 1}.description`, descriptionValue: section.description, })), styleTexts: [ { key: content.heroSectionStyleClassKey, value: content.heroSectionStyleClass, }, { key: content.heroTitleStyleClassKey, value: content.heroTitleStyleClass, }, { key: content.heroSubtitleStyleClassKey, value: content.heroSubtitleStyleClass, }, { key: content.descriptionsContainerStyleClassKey, value: content.descriptionsContainerStyleClass, }, ...content.stats.flatMap((stat) => [ { key: stat.styleClassKey, value: stat.styleClass, }, { key: stat.valueStyleClassKey, value: stat.valueStyleClass, }, { key: stat.labelStyleClassKey, value: stat.labelStyleClass, }, ]), ...content.descriptions.map((item) => ({ key: item.styleClassKey, value: item.styleClass, })), ...content.sections.flatMap((section) => [ { key: section.cardStyleClassKey, value: section.cardStyleClass, }, { key: section.titleStyleClassKey, value: section.titleStyleClass, }, { key: section.descriptionStyleClassKey, value: section.descriptionStyleClass, }, ]), ], }) await getConfig(false) setSelectedBlockId(null) toast.push( {translate('::ListForms.FormBilgileriKaydedildi')} , { placement: 'top-end', }, ) } catch (error) { console.error('About tasarimi kaydedilemedi:', error) } finally { setIsSaving(false) } } const handleLanguageChange = (language: string) => { // Global locale changes asynchronously fetch fresh localization texts. // Keep designer language synced from store after that refresh. setLang(language) } const handleSelectBlock = (blockId: string) => { setSelectedBlockId(blockId) if (!isPanelVisible) { setIsPanelVisible(true) } } if (loading) { return (
) } return ( <>
{isDesignMode && !isPanelVisible && ( )} {/* Hero Section */}

{content?.heroTitle}

{content?.heroSubtitle}

{/* Stats Section */}
{content?.stats.map((stat, index) => { const IconComponent = navigationIcon[stat.icon || ''] return (
{IconComponent && ( )}
{stat.value}
{stat.label}
) })}
{/* Main Content */}
{content?.descriptions.map((item, index) => (

{item.text}

))}
{content?.sections.map((section, index) => (

{section.title}

{section.description}

))}
0 ? languageOptions : supportedLanguages.map((language) => ({ key: language, cultureName: language, displayName: language.toUpperCase(), })) } onClose={() => setIsPanelVisible(false)} onSave={handleSaveAndExit} onLanguageChange={handleLanguageChange} onReset={resetContent} onFieldChange={handleFieldChange} />
) } export default About