2026-02-24 20:44:16 +00:00
|
|
|
|
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'
|
2026-03-17 09:54:25 +00:00
|
|
|
|
import { getAbout, saveAboutPage } from '@/services/about'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
|
|
|
|
import Loading from '@/components/shared/Loading'
|
|
|
|
|
|
import { APP_NAME } from '@/constants/app.constant'
|
2026-03-17 11:46:20 +00:00
|
|
|
|
import { Notification, toast } from '@/components/ui'
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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
|
2026-03-17 13:35:58 +00:00
|
|
|
|
styleClassKey: string
|
|
|
|
|
|
styleClass: string
|
|
|
|
|
|
valueStyleClassKey: string
|
|
|
|
|
|
valueStyleClass: string
|
|
|
|
|
|
labelStyleClassKey: string
|
|
|
|
|
|
labelStyleClass: string
|
2026-03-17 09:54:25 +00:00
|
|
|
|
useCounter?: boolean
|
|
|
|
|
|
counterEnd?: string
|
|
|
|
|
|
counterSuffix?: string
|
|
|
|
|
|
counterDuration?: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface AboutDescriptionContent {
|
|
|
|
|
|
key: string
|
|
|
|
|
|
text: string
|
2026-03-17 13:35:58 +00:00
|
|
|
|
styleClassKey: string
|
|
|
|
|
|
styleClass: string
|
2026-03-17 09:54:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface AboutSectionContent {
|
|
|
|
|
|
title: string
|
|
|
|
|
|
description: string
|
|
|
|
|
|
titleKey: string
|
|
|
|
|
|
descriptionKey: string
|
2026-03-17 13:35:58 +00:00
|
|
|
|
cardStyleClassKey: string
|
|
|
|
|
|
cardStyleClass: string
|
|
|
|
|
|
titleStyleClassKey: string
|
|
|
|
|
|
titleStyleClass: string
|
|
|
|
|
|
descriptionStyleClassKey: string
|
|
|
|
|
|
descriptionStyleClass: string
|
2026-03-17 09:54:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface AboutContent {
|
|
|
|
|
|
heroTitle: string
|
|
|
|
|
|
heroTitleKey: string
|
2026-03-17 13:35:58 +00:00
|
|
|
|
heroTitleStyleClassKey: string
|
|
|
|
|
|
heroTitleStyleClass: string
|
2026-03-17 09:54:25 +00:00
|
|
|
|
heroSubtitle: string
|
|
|
|
|
|
heroSubtitleKey: string
|
2026-03-17 13:35:58 +00:00
|
|
|
|
heroSubtitleStyleClassKey: string
|
|
|
|
|
|
heroSubtitleStyleClass: string
|
2026-03-17 09:54:25 +00:00
|
|
|
|
heroImage: string
|
|
|
|
|
|
heroImageKey: string
|
2026-03-17 13:35:58 +00:00
|
|
|
|
heroSectionStyleClassKey: string
|
|
|
|
|
|
heroSectionStyleClass: string
|
|
|
|
|
|
descriptionsContainerStyleClassKey: string
|
|
|
|
|
|
descriptionsContainerStyleClass: string
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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'
|
2026-03-17 13:35:58 +00:00
|
|
|
|
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'
|
2026-03-17 09:54:25 +00:00
|
|
|
|
|
|
|
|
|
|
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,
|
2026-03-17 13:35:58 +00:00
|
|
|
|
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',
|
|
|
|
|
|
),
|
2026-03-17 09:54:25 +00:00
|
|
|
|
heroSubtitle: resolveLocalizedValue(translate, ABOUT_HERO_SUBTITLE_KEY),
|
|
|
|
|
|
heroSubtitleKey: ABOUT_HERO_SUBTITLE_KEY,
|
2026-03-17 13:35:58 +00:00
|
|
|
|
heroSubtitleStyleClassKey: ABOUT_HERO_SUBTITLE_STYLE_KEY,
|
|
|
|
|
|
heroSubtitleStyleClass: resolveLocalizedValue(
|
|
|
|
|
|
translate,
|
|
|
|
|
|
ABOUT_HERO_SUBTITLE_STYLE_KEY,
|
|
|
|
|
|
'text-xl max-w-3xl ml-4',
|
|
|
|
|
|
),
|
2026-03-17 09:54:25 +00:00
|
|
|
|
heroImage: resolveLocalizedValue(translate, ABOUT_HERO_IMAGE_KEY, ABOUT_HERO_IMAGE),
|
|
|
|
|
|
heroImageKey: ABOUT_HERO_IMAGE_KEY,
|
2026-03-17 13:35:58 +00:00
|
|
|
|
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',
|
|
|
|
|
|
),
|
2026-03-17 09:54:25 +00:00
|
|
|
|
stats:
|
2026-03-17 13:35:58 +00:00
|
|
|
|
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',
|
|
|
|
|
|
),
|
2026-03-17 09:54:25 +00:00
|
|
|
|
icon: stat.icon || '',
|
|
|
|
|
|
value: stat.value,
|
|
|
|
|
|
label: resolveLocalizedValue(translate, stat.labelKey, stat.labelKey),
|
|
|
|
|
|
labelKey:
|
|
|
|
|
|
(isLikelyLocalizationKey(stat.labelKey) ? stat.labelKey : undefined) ||
|
2026-03-17 13:35:58 +00:00
|
|
|
|
`Public.about.dynamic.stat.${index + 1}.label`,
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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),
|
2026-03-17 13:35:58 +00:00
|
|
|
|
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',
|
|
|
|
|
|
),
|
2026-03-17 09:54:25 +00:00
|
|
|
|
})) ?? [],
|
|
|
|
|
|
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`,
|
2026-03-17 13:35:58 +00:00
|
|
|
|
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',
|
|
|
|
|
|
),
|
2026-03-17 09:54:25 +00:00
|
|
|
|
})) ?? [],
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
const About: React.FC = () => {
|
|
|
|
|
|
const { translate } = useLocalization()
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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)),
|
|
|
|
|
|
)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const [loading, setLoading] = useState(true)
|
2026-03-17 09:54:25 +00:00
|
|
|
|
const [isSaving, setIsSaving] = useState(false)
|
|
|
|
|
|
const [isPanelVisible, setIsPanelVisible] = useState(true)
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const [about, setAbout] = useState<AboutDto>()
|
|
|
|
|
|
|
|
|
|
|
|
const iconColors = [
|
|
|
|
|
|
'text-blue-600',
|
|
|
|
|
|
'text-red-600',
|
|
|
|
|
|
'text-green-600',
|
|
|
|
|
|
'text-purple-600',
|
|
|
|
|
|
'text-yellow-600',
|
|
|
|
|
|
'text-indigo-600',
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-03-17 09:54:25 +00:00
|
|
|
|
function getIconColor(index: number) {
|
|
|
|
|
|
return iconColors[index % iconColors.length]
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 09:54:25 +00:00
|
|
|
|
const initialContent = !loading ? buildAboutContent(about, translate) : null
|
|
|
|
|
|
const {
|
|
|
|
|
|
content,
|
|
|
|
|
|
isDesignMode,
|
|
|
|
|
|
selectedBlockId,
|
|
|
|
|
|
selectedLanguage,
|
|
|
|
|
|
supportedLanguages,
|
|
|
|
|
|
setContent,
|
|
|
|
|
|
setSelectedBlockId,
|
|
|
|
|
|
resetContent,
|
|
|
|
|
|
} = useDesignerState<AboutContent>('about', initialContent, {
|
|
|
|
|
|
currentLanguage,
|
|
|
|
|
|
supportedLanguages: editorLanguages,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
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()
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
2026-03-17 09:54:25 +00:00
|
|
|
|
const updateContent = (updater: (current: AboutContent) => AboutContent) => {
|
|
|
|
|
|
setContent((current) => {
|
|
|
|
|
|
if (!current) {
|
|
|
|
|
|
return current
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return updater(current)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleFieldChange = (fieldKey: string, value: string | string[]) => {
|
|
|
|
|
|
updateContent((current) => {
|
2026-03-17 13:35:58 +00:00
|
|
|
|
if (
|
|
|
|
|
|
fieldKey === 'heroTitle' ||
|
|
|
|
|
|
fieldKey === 'heroSubtitle' ||
|
|
|
|
|
|
fieldKey === 'heroImage' ||
|
|
|
|
|
|
fieldKey === 'heroSectionStyleClass' ||
|
|
|
|
|
|
fieldKey === 'heroTitleStyleClass' ||
|
|
|
|
|
|
fieldKey === 'heroSubtitleStyleClass' ||
|
|
|
|
|
|
fieldKey === 'descriptionsContainerStyleClass'
|
|
|
|
|
|
) {
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 13:35:58 +00:00
|
|
|
|
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,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 09:54:25 +00:00
|
|
|
|
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,
|
|
|
|
|
|
},
|
2026-03-17 13:35:58 +00:00
|
|
|
|
{
|
|
|
|
|
|
key: 'heroTitleStyleClass',
|
|
|
|
|
|
label: content.heroTitleStyleClassKey,
|
|
|
|
|
|
type: 'text',
|
|
|
|
|
|
value: content.heroTitleStyleClass,
|
|
|
|
|
|
},
|
2026-03-17 09:54:25 +00:00
|
|
|
|
{
|
|
|
|
|
|
key: 'heroSubtitle',
|
|
|
|
|
|
label: content.heroSubtitleKey,
|
|
|
|
|
|
type: 'textarea',
|
|
|
|
|
|
value: content.heroSubtitle,
|
|
|
|
|
|
},
|
2026-03-17 13:35:58 +00:00
|
|
|
|
{
|
|
|
|
|
|
key: 'heroSubtitleStyleClass',
|
|
|
|
|
|
label: content.heroSubtitleStyleClassKey,
|
|
|
|
|
|
type: 'text',
|
|
|
|
|
|
value: content.heroSubtitleStyleClass,
|
|
|
|
|
|
},
|
2026-03-17 09:54:25 +00:00
|
|
|
|
{
|
|
|
|
|
|
key: 'heroImage',
|
|
|
|
|
|
label: content.heroImageKey,
|
|
|
|
|
|
type: 'image',
|
|
|
|
|
|
value: content.heroImage,
|
|
|
|
|
|
},
|
2026-03-17 13:35:58 +00:00
|
|
|
|
{
|
|
|
|
|
|
key: 'heroSectionStyleClass',
|
|
|
|
|
|
label: content.heroSectionStyleClassKey,
|
|
|
|
|
|
type: 'text',
|
|
|
|
|
|
value: content.heroSectionStyleClass,
|
|
|
|
|
|
},
|
2026-03-17 09:54:25 +00:00
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (selectedBlockId === 'descriptions') {
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: 'descriptions',
|
|
|
|
|
|
title: 'Public.about.description.*',
|
|
|
|
|
|
description: 'Orta bolumdeki aciklama metinlerini duzenleyin.',
|
2026-03-17 13:35:58 +00:00
|
|
|
|
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,
|
|
|
|
|
|
},
|
|
|
|
|
|
]),
|
|
|
|
|
|
],
|
2026-03-17 09:54:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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',
|
2026-03-29 08:59:07 +00:00
|
|
|
|
label: translate('::App.Listform.ListformField.Value'),
|
2026-03-17 09:54:25 +00:00
|
|
|
|
type: 'text',
|
|
|
|
|
|
value: stat.value
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'label',
|
|
|
|
|
|
label: translate('::' + stat.labelKey),
|
|
|
|
|
|
type: 'text',
|
|
|
|
|
|
value: stat.label,
|
|
|
|
|
|
},
|
2026-03-17 13:35:58 +00:00
|
|
|
|
{
|
|
|
|
|
|
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,
|
|
|
|
|
|
},
|
2026-03-17 09:54:25 +00:00
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
},
|
2026-03-17 13:35:58 +00:00
|
|
|
|
{
|
|
|
|
|
|
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,
|
|
|
|
|
|
},
|
2026-03-17 09:54:25 +00:00
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
})),
|
2026-03-17 13:35:58 +00:00
|
|
|
|
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,
|
|
|
|
|
|
},
|
|
|
|
|
|
]),
|
|
|
|
|
|
],
|
2026-03-17 09:54:25 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
await getConfig(false)
|
|
|
|
|
|
setSelectedBlockId(null)
|
2026-03-17 11:46:20 +00:00
|
|
|
|
|
|
|
|
|
|
toast.push(
|
|
|
|
|
|
<Notification type="success" duration={2000}>
|
|
|
|
|
|
{translate('::ListForms.FormBilgileriKaydedildi')}
|
|
|
|
|
|
</Notification>,
|
|
|
|
|
|
{
|
|
|
|
|
|
placement: 'top-end',
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-17 09:54:25 +00:00
|
|
|
|
} 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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
if (loading) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex items-center justify-center min-h-screen bg-gray-50">
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
|
<Loading loading={loading} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Helmet
|
|
|
|
|
|
titleTemplate={`%s | ${APP_NAME}`}
|
|
|
|
|
|
title={translate('::App.About')}
|
|
|
|
|
|
defaultTitle={APP_NAME}
|
|
|
|
|
|
></Helmet>
|
|
|
|
|
|
|
2026-03-17 09:54:25 +00:00
|
|
|
|
<div className={`min-h-screen bg-gray-50 ${isDesignMode && isPanelVisible ? 'xl:pr-[420px]' : ''}`}>
|
|
|
|
|
|
{isDesignMode && !isPanelVisible && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => setIsPanelVisible(true)}
|
|
|
|
|
|
className="fixed right-4 top-1/2 z-40 -translate-y-1/2 rounded-full bg-slate-900 px-4 py-2 text-sm font-semibold text-white shadow-xl"
|
|
|
|
|
|
>
|
|
|
|
|
|
{translate('::Public.designer.showPanel')}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
{/* Hero Section */}
|
2026-03-17 09:54:25 +00:00
|
|
|
|
<SelectableBlock
|
|
|
|
|
|
id="hero"
|
|
|
|
|
|
isActive={selectedBlockId === 'hero'}
|
|
|
|
|
|
isDesignMode={isDesignMode}
|
|
|
|
|
|
onSelect={handleSelectBlock}
|
|
|
|
|
|
>
|
2026-03-17 13:35:58 +00:00
|
|
|
|
<div className={content?.heroSectionStyleClass || 'relative bg-blue-900 text-white py-12'}>
|
2026-03-17 09:54:25 +00:00
|
|
|
|
<div
|
|
|
|
|
|
className="absolute inset-0 opacity-20"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
backgroundImage: `url("${content?.heroImage ?? ABOUT_HERO_IMAGE}")`,
|
|
|
|
|
|
backgroundSize: 'cover',
|
|
|
|
|
|
backgroundPosition: 'center',
|
|
|
|
|
|
}}
|
|
|
|
|
|
></div>
|
|
|
|
|
|
<div className="container mx-auto pt-20 relative">
|
2026-03-17 13:35:58 +00:00
|
|
|
|
<h1 className={content?.heroTitleStyleClass || 'text-5xl font-bold ml-4 mt-3 mb-2 text-white'}>
|
2026-03-17 09:54:25 +00:00
|
|
|
|
{content?.heroTitle}
|
|
|
|
|
|
</h1>
|
2026-03-17 13:35:58 +00:00
|
|
|
|
<p className={content?.heroSubtitleStyleClass || 'text-xl max-w-3xl ml-4'}>{content?.heroSubtitle}</p>
|
2026-03-17 09:54:25 +00:00
|
|
|
|
</div>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
2026-03-17 09:54:25 +00:00
|
|
|
|
</SelectableBlock>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
{/* Stats Section */}
|
|
|
|
|
|
<div className="py-10 bg-white">
|
|
|
|
|
|
<div className="container mx-auto px-4">
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
2026-03-17 09:54:25 +00:00
|
|
|
|
{content?.stats.map((stat, index) => {
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const IconComponent = navigationIcon[stat.icon || '']
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-03-17 09:54:25 +00:00
|
|
|
|
<SelectableBlock
|
|
|
|
|
|
key={index}
|
|
|
|
|
|
id={`stat-${index}`}
|
|
|
|
|
|
isActive={selectedBlockId === `stat-${index}`}
|
|
|
|
|
|
isDesignMode={isDesignMode}
|
|
|
|
|
|
onSelect={handleSelectBlock}
|
|
|
|
|
|
>
|
2026-03-17 13:35:58 +00:00
|
|
|
|
<div className={stat.styleClass || 'text-center rounded-xl px-4 py-6'}>
|
2026-03-17 09:54:25 +00:00
|
|
|
|
{IconComponent && (
|
|
|
|
|
|
<IconComponent
|
|
|
|
|
|
className={`w-12 h-12 mx-auto mb-4 ${getIconColor(index)}`}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2026-03-17 13:35:58 +00:00
|
|
|
|
<div className={stat.valueStyleClass || 'text-4xl font-bold text-gray-900 mb-2'}>{stat.value}</div>
|
|
|
|
|
|
<div className={stat.labelStyleClass || 'text-gray-600'}>{stat.label}</div>
|
2026-03-17 09:54:25 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</SelectableBlock>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
)
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Main Content */}
|
|
|
|
|
|
<div className="py-6">
|
|
|
|
|
|
<div className="container mx-auto px-4">
|
|
|
|
|
|
<div className="mb-6">
|
2026-03-17 09:54:25 +00:00
|
|
|
|
<SelectableBlock
|
|
|
|
|
|
id="descriptions"
|
|
|
|
|
|
isActive={selectedBlockId === 'descriptions'}
|
|
|
|
|
|
isDesignMode={isDesignMode}
|
|
|
|
|
|
onSelect={handleSelectBlock}
|
|
|
|
|
|
>
|
2026-03-17 13:35:58 +00:00
|
|
|
|
<div className={content?.descriptionsContainerStyleClass || 'p-5 mx-auto text-gray-800 text-lg leading-relaxed shadow-md bg-white border-l-4 border-blue-600'}>
|
|
|
|
|
|
{content?.descriptions.map((item, index) => (
|
|
|
|
|
|
<p key={item.key || `description-${index}`} className={item.styleClass || ''}>
|
|
|
|
|
|
{item.text}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
))}
|
2026-03-17 09:54:25 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</SelectableBlock>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
2026-03-17 09:54:25 +00:00
|
|
|
|
{content?.sections.map((section, index) => (
|
|
|
|
|
|
<SelectableBlock
|
|
|
|
|
|
key={index}
|
|
|
|
|
|
id={`section-${index}`}
|
|
|
|
|
|
isActive={selectedBlockId === `section-${index}`}
|
|
|
|
|
|
isDesignMode={isDesignMode}
|
|
|
|
|
|
onSelect={handleSelectBlock}
|
|
|
|
|
|
>
|
2026-03-17 13:35:58 +00:00
|
|
|
|
<div className={section.cardStyleClass || 'bg-white p-8 rounded-xl shadow-lg'}>
|
|
|
|
|
|
<h3 className={section.titleStyleClass || 'text-2xl font-bold text-gray-900 mb-4'}>{section.title}</h3>
|
|
|
|
|
|
<p className={section.descriptionStyleClass || 'text-gray-700'}>{section.description}</p>
|
2026-03-17 09:54:25 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</SelectableBlock>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-17 09:54:25 +00:00
|
|
|
|
|
|
|
|
|
|
<DesignerDrawer
|
|
|
|
|
|
isOpen={isDesignMode && isPanelVisible}
|
|
|
|
|
|
selection={selectedSelection}
|
|
|
|
|
|
pageTitle="About"
|
|
|
|
|
|
selectedLanguage={selectedLanguage}
|
|
|
|
|
|
languages={
|
|
|
|
|
|
languageOptions.length > 0
|
|
|
|
|
|
? languageOptions
|
|
|
|
|
|
: supportedLanguages.map((language) => ({
|
|
|
|
|
|
key: language,
|
|
|
|
|
|
cultureName: language,
|
|
|
|
|
|
displayName: language.toUpperCase(),
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
onClose={() => setIsPanelVisible(false)}
|
|
|
|
|
|
onSave={handleSaveAndExit}
|
|
|
|
|
|
onLanguageChange={handleLanguageChange}
|
|
|
|
|
|
onReset={resetContent}
|
|
|
|
|
|
onFieldChange={handleFieldChange}
|
|
|
|
|
|
/>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default About
|