sozsoft-platform/ui/src/views/public/Home.tsx

1229 lines
42 KiB
TypeScript
Raw Normal View History

import React, { useEffect, useState } from 'react'
2026-02-24 20:44:16 +00:00
import { Link } from 'react-router-dom'
import {
FaArrowRight,
FaChevronLeft,
FaChevronRight,
} from 'react-icons/fa'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant'
import { useStoreActions, useStoreState } from '@/store'
import Loading from '@/components/shared/Loading'
import navigationIcon from '@/proxy/menus/navigation-icon.config'
import DesignerDrawer from './designer/DesignerDrawer'
import SelectableBlock from './designer/SelectableBlock'
import { DesignerSelection } from './designer/types'
import { useDesignerState } from './designer/useDesignerState'
import { getHome, HomeDto, saveHomePage } from '@/services/home.service'
import { Button, Notification, toast } from '@/components/ui'
import { useDemo } from '@/contexts/DemoContext'
2026-02-24 20:44:16 +00:00
interface HomeSlideServiceContent {
icon: string
title: string
titleKey: string
description: string
descriptionKey: string
2026-03-17 13:35:58 +00:00
styleClass: string
}
interface HomeSlideContent {
title: string
titleKey: string
subtitle: string
subtitleKey: string
2026-03-17 13:35:58 +00:00
styleClass: string
services: HomeSlideServiceContent[]
}
interface HomeFeatureContent {
icon: string
title: string
titleKey: string
description: string
descriptionKey: string
2026-03-17 13:35:58 +00:00
styleClass: string
}
interface HomeSolutionContent {
icon: string
colorClass: string
title: string
titleKey: string
description: string
descriptionKey: string
2026-03-17 13:35:58 +00:00
styleClass: string
}
interface HomeContent {
heroBackgroundImage: string
heroBackgroundImageKey: string
heroPrimaryCtaLabel: string
heroPrimaryCtaKey: string
2026-03-17 13:35:58 +00:00
heroPrimaryCtaStyle: string
heroPrimaryCtaStyleKey: string
heroSecondaryCtaLabel: string
heroSecondaryCtaKey: string
2026-03-17 13:35:58 +00:00
heroSecondaryCtaStyle: string
heroSecondaryCtaStyleKey: string
featuresTitle: string
featuresTitleKey: string
2026-03-17 13:35:58 +00:00
featuresTitleStyle: string
featuresTitleStyleKey: string
featuresSubtitle: string
featuresSubtitleKey: string
2026-03-17 13:35:58 +00:00
featuresSubtitleStyle: string
featuresSubtitleStyleKey: string
solutionsTitle: string
solutionsTitleKey: string
2026-03-17 13:35:58 +00:00
solutionsTitleStyle: string
solutionsTitleStyleKey: string
solutionsSubtitle: string
solutionsSubtitleKey: string
2026-03-17 13:35:58 +00:00
solutionsSubtitleStyle: string
solutionsSubtitleStyleKey: string
ctaTitle: string
ctaTitleKey: string
2026-03-17 13:35:58 +00:00
ctaTitleStyle: string
ctaTitleStyleKey: string
ctaSubtitle: string
ctaSubtitleKey: string
2026-03-17 13:35:58 +00:00
ctaSubtitleStyle: string
ctaSubtitleStyleKey: string
ctaButtonLabel: string
ctaButtonLabelKey: string
2026-03-17 13:35:58 +00:00
ctaButtonStyle: string
ctaButtonStyleKey: string
slides: HomeSlideContent[]
features: HomeFeatureContent[]
solutions: HomeSolutionContent[]
}
const HOME_HERO_DEFAULT_IMAGE =
'https://images.pexels.com/photos/3183150/pexels-photo-3183150.jpeg?auto=compress&cs=tinysrgb&w=1920'
2026-03-17 13:35:58 +00:00
const HOME_HERO_PRIMARY_CTA_STYLE_KEY = 'Public.home.hero.primaryCta.style'
const HOME_HERO_SECONDARY_CTA_STYLE_KEY = 'Public.home.hero.secondaryCta.style'
const HOME_FEATURES_TITLE_STYLE_KEY = 'Public.home.features.title.style'
const HOME_FEATURES_SUBTITLE_STYLE_KEY = 'Public.home.features.subtitle.style'
const HOME_SOLUTIONS_TITLE_STYLE_KEY = 'Public.home.solutions.title.style'
const HOME_SOLUTIONS_SUBTITLE_STYLE_KEY = 'Public.home.solutions.subtitle.style'
const HOME_CTA_TITLE_STYLE_KEY = 'Public.home.cta.title.style'
const HOME_CTA_SUBTITLE_STYLE_KEY = 'Public.home.cta.subtitle.style'
const HOME_CTA_BUTTON_STYLE_KEY = 'Public.home.cta.button.style'
const HOME_HERO_PRIMARY_CTA_STYLE_DEFAULT =
'inline-flex items-center justify-center px-8 py-4 bg-gradient-to-r from-blue-500 to-purple-500 hover:from-blue-600 hover:to-purple-600 text-white rounded-lg font-semibold transition-all transform hover:scale-105'
const HOME_HERO_SECONDARY_CTA_STYLE_DEFAULT =
'inline-flex items-center justify-center px-8 py-4 bg-white/10 hover:bg-white/20 text-white rounded-lg font-semibold backdrop-blur-sm transition-all transform hover:scale-105'
2026-05-26 16:09:26 +00:00
const HOME_FEATURES_TITLE_STYLE_DEFAULT = 'text-4xl font-bold text-gray-900 dark:text-gray-100 mb-4'
const HOME_FEATURES_SUBTITLE_STYLE_DEFAULT = 'text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto'
const HOME_SOLUTIONS_TITLE_STYLE_DEFAULT = 'text-4xl font-bold text-gray-900 dark:text-gray-100 mb-4'
const HOME_SOLUTIONS_SUBTITLE_STYLE_DEFAULT = 'text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto'
2026-03-17 13:35:58 +00:00
const HOME_CTA_TITLE_STYLE_DEFAULT = 'text-3xl font-bold text-white mb-4'
const HOME_CTA_SUBTITLE_STYLE_DEFAULT = 'text-white text-lg mb-8'
const HOME_CTA_BUTTON_STYLE_DEFAULT =
'bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-blue-50 transition-colors'
const HOME_SLIDE_STYLE_DEFAULT = 'max-w-4xl mx-auto text-center'
const HOME_SLIDE_SERVICE_STYLE_DEFAULT =
'bg-white/5 backdrop-blur-sm rounded-2xl p-8 text-center hover:scale-105 hover:bg-white/10 transition-all'
const HOME_FEATURE_CARD_STYLE_DEFAULT =
2026-05-26 16:09:26 +00:00
'p-8 bg-white dark:bg-gray-900 rounded-xl shadow-lg hover:shadow-xl transition-shadow dark:shadow-gray-950/40'
2026-03-17 13:35:58 +00:00
const HOME_SOLUTION_CARD_STYLE_DEFAULT = 'p-8 h-full rounded-2xl'
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
}
2026-02-24 20:44:16 +00:00
if (!isLikelyLocalizationKey(keyOrValue)) {
return keyOrValue
}
const translatedValue = translate('::' + keyOrValue)
return translatedValue === keyOrValue ? fallback || keyOrValue : translatedValue
}
function buildHomeContent(home: HomeDto | undefined, translate: (key: string) => string): HomeContent {
2026-03-17 13:35:58 +00:00
const slideItems = home?.slidesDto || []
const featureItems = home?.featuresDto || []
const solutionItems = home?.solutionsDto || []
return {
heroBackgroundImage: resolveLocalizedValue(
translate,
2026-03-17 13:35:58 +00:00
home?.heroBackgroundImageKey,
HOME_HERO_DEFAULT_IMAGE,
),
2026-03-17 13:35:58 +00:00
heroBackgroundImageKey: home?.heroBackgroundImageKey || '',
heroPrimaryCtaLabel: resolveLocalizedValue(translate, home?.heroPrimaryCtaKey, ''),
heroPrimaryCtaKey: home?.heroPrimaryCtaKey || '',
heroPrimaryCtaStyle: resolveLocalizedValue(
translate,
HOME_HERO_PRIMARY_CTA_STYLE_KEY,
HOME_HERO_PRIMARY_CTA_STYLE_DEFAULT,
),
heroPrimaryCtaStyleKey: HOME_HERO_PRIMARY_CTA_STYLE_KEY,
heroSecondaryCtaLabel: resolveLocalizedValue(translate, home?.heroSecondaryCtaKey, ''),
heroSecondaryCtaKey: home?.heroSecondaryCtaKey || '',
heroSecondaryCtaStyle: resolveLocalizedValue(
translate,
HOME_HERO_SECONDARY_CTA_STYLE_KEY,
HOME_HERO_SECONDARY_CTA_STYLE_DEFAULT,
),
heroSecondaryCtaStyleKey: HOME_HERO_SECONDARY_CTA_STYLE_KEY,
featuresTitle: resolveLocalizedValue(translate, home?.featuresTitleKey, ''),
featuresTitleKey: home?.featuresTitleKey || '',
featuresTitleStyle: resolveLocalizedValue(
translate,
2026-03-17 13:35:58 +00:00
HOME_FEATURES_TITLE_STYLE_KEY,
HOME_FEATURES_TITLE_STYLE_DEFAULT,
),
2026-03-17 13:35:58 +00:00
featuresTitleStyleKey: HOME_FEATURES_TITLE_STYLE_KEY,
featuresSubtitle: resolveLocalizedValue(translate, home?.featuresSubtitleKey, ''),
featuresSubtitleKey: home?.featuresSubtitleKey || '',
featuresSubtitleStyle: resolveLocalizedValue(
translate,
2026-03-17 13:35:58 +00:00
HOME_FEATURES_SUBTITLE_STYLE_KEY,
HOME_FEATURES_SUBTITLE_STYLE_DEFAULT,
),
2026-03-17 13:35:58 +00:00
featuresSubtitleStyleKey: HOME_FEATURES_SUBTITLE_STYLE_KEY,
solutionsTitle: resolveLocalizedValue(translate, home?.solutionsTitleKey, ''),
solutionsTitleKey: home?.solutionsTitleKey || '',
solutionsTitleStyle: resolveLocalizedValue(
translate,
2026-03-17 13:35:58 +00:00
HOME_SOLUTIONS_TITLE_STYLE_KEY,
HOME_SOLUTIONS_TITLE_STYLE_DEFAULT,
),
2026-03-17 13:35:58 +00:00
solutionsTitleStyleKey: HOME_SOLUTIONS_TITLE_STYLE_KEY,
solutionsSubtitle: resolveLocalizedValue(translate, home?.solutionsSubtitleKey, ''),
solutionsSubtitleKey: home?.solutionsSubtitleKey || '',
solutionsSubtitleStyle: resolveLocalizedValue(
translate,
2026-03-17 13:35:58 +00:00
HOME_SOLUTIONS_SUBTITLE_STYLE_KEY,
HOME_SOLUTIONS_SUBTITLE_STYLE_DEFAULT,
),
2026-03-17 13:35:58 +00:00
solutionsSubtitleStyleKey: HOME_SOLUTIONS_SUBTITLE_STYLE_KEY,
ctaTitle: resolveLocalizedValue(translate, home?.ctaTitleKey, ''),
ctaTitleKey: home?.ctaTitleKey || '',
ctaTitleStyle: resolveLocalizedValue(
translate,
HOME_CTA_TITLE_STYLE_KEY,
HOME_CTA_TITLE_STYLE_DEFAULT,
),
ctaTitleStyleKey: HOME_CTA_TITLE_STYLE_KEY,
ctaSubtitle: resolveLocalizedValue(translate, home?.ctaSubtitleKey, ''),
ctaSubtitleKey: home?.ctaSubtitleKey || '',
ctaSubtitleStyle: resolveLocalizedValue(
translate,
HOME_CTA_SUBTITLE_STYLE_KEY,
HOME_CTA_SUBTITLE_STYLE_DEFAULT,
),
ctaSubtitleStyleKey: HOME_CTA_SUBTITLE_STYLE_KEY,
ctaButtonLabel: resolveLocalizedValue(translate, home?.ctaButtonLabelKey, ''),
ctaButtonLabelKey: home?.ctaButtonLabelKey || '',
ctaButtonStyle: resolveLocalizedValue(
translate,
HOME_CTA_BUTTON_STYLE_KEY,
HOME_CTA_BUTTON_STYLE_DEFAULT,
),
ctaButtonStyleKey: HOME_CTA_BUTTON_STYLE_KEY,
slides: slideItems.map((slide) => ({
title: resolveLocalizedValue(translate, slide.titleKey, slide.titleKey),
2026-03-17 13:35:58 +00:00
titleKey: slide.titleKey || '',
subtitle: resolveLocalizedValue(translate, slide.subtitleKey, slide.subtitleKey),
2026-03-17 13:35:58 +00:00
subtitleKey: slide.subtitleKey || '',
styleClass: slide.styleClass || HOME_SLIDE_STYLE_DEFAULT,
services: (slide.services || []).map((service) => ({
icon: service.icon || '',
title: resolveLocalizedValue(translate, service.titleKey, service.titleKey),
2026-03-17 13:35:58 +00:00
titleKey: service.titleKey || '',
description: resolveLocalizedValue(translate, service.descriptionKey, service.descriptionKey),
2026-03-17 13:35:58 +00:00
descriptionKey: service.descriptionKey || '',
styleClass: service.styleClass || HOME_SLIDE_SERVICE_STYLE_DEFAULT,
})),
})),
2026-03-17 13:35:58 +00:00
features: featureItems.map((feature) => ({
icon: feature.icon || '',
title: resolveLocalizedValue(translate, feature.titleKey, feature.titleKey),
2026-03-17 13:35:58 +00:00
titleKey: feature.titleKey || '',
description: resolveLocalizedValue(translate, feature.descriptionKey, feature.descriptionKey),
2026-03-17 13:35:58 +00:00
descriptionKey: feature.descriptionKey || '',
styleClass: feature.styleClass || HOME_FEATURE_CARD_STYLE_DEFAULT,
})),
2026-03-17 13:35:58 +00:00
solutions: solutionItems.map((solution) => ({
icon: solution.icon || '',
colorClass: solution.colorClass || '',
title: resolveLocalizedValue(translate, solution.titleKey, solution.titleKey),
2026-03-17 13:35:58 +00:00
titleKey: solution.titleKey || '',
description: resolveLocalizedValue(translate, solution.descriptionKey, solution.descriptionKey),
2026-03-17 13:35:58 +00:00
descriptionKey: solution.descriptionKey || '',
styleClass: solution.styleClass || HOME_SOLUTION_CARD_STYLE_DEFAULT,
})),
}
}
const Home: React.FC = () => {
const { openDemo } = useDemo()
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 [home, setHome] = useState<HomeDto>()
const [loading, setLoading] = useState(true)
const [isSaving, setIsSaving] = useState(false)
const [isPanelVisible, setIsPanelVisible] = useState(true)
const [currentSlide, setCurrentSlide] = useState(0)
const initialContent = !loading ? buildHomeContent(home, translate) : null
const {
content,
isDesignMode,
selectedBlockId,
selectedLanguage,
supportedLanguages,
setContent,
setSelectedBlockId,
resetContent,
} = useDesignerState<HomeContent>('home', initialContent, {
currentLanguage,
supportedLanguages: editorLanguages,
})
useEffect(() => {
setLoading(true)
const fetchHome = async () => {
try {
const result = await getHome()
setHome(result.data)
} catch (error) {
console.error('Home alinirken hata olustu:', error)
} finally {
setLoading(false)
}
}
fetchHome()
}, [])
useEffect(() => {
if (!content?.slides?.length) {
return
}
if (currentSlide >= content.slides.length) {
setCurrentSlide(0)
}
}, [content?.slides.length, currentSlide])
useEffect(() => {
if (!content?.slides?.length) {
return
}
const timer = setInterval(() => {
setCurrentSlide((prev) => (prev + 1) % content.slides.length)
}, 10000)
return () => clearInterval(timer)
}, [content?.slides.length])
const nextSlide = () => {
const size = content?.slides.length || 1
setCurrentSlide((prev) => (prev + 1) % size)
}
const prevSlide = () => {
const size = content?.slides.length || 1
setCurrentSlide((prev) => (prev - 1 + size) % size)
}
const updateContent = (updater: (current: HomeContent) => HomeContent) => {
setContent((current) => {
if (!current) {
return current
}
return updater(current)
})
}
const handleFieldChange = (fieldKey: string, value: string | string[]) => {
const nextValue = value as string
updateContent((current) => {
if (
fieldKey === 'heroBackgroundImage' ||
fieldKey === 'heroPrimaryCtaLabel' ||
2026-03-17 13:35:58 +00:00
fieldKey === 'heroPrimaryCtaStyle' ||
fieldKey === 'heroSecondaryCtaLabel' ||
2026-03-17 13:35:58 +00:00
fieldKey === 'heroSecondaryCtaStyle' ||
fieldKey === 'featuresTitle' ||
2026-03-17 13:35:58 +00:00
fieldKey === 'featuresTitleStyle' ||
fieldKey === 'featuresSubtitle' ||
2026-03-17 13:35:58 +00:00
fieldKey === 'featuresSubtitleStyle' ||
fieldKey === 'solutionsTitle' ||
2026-03-17 13:35:58 +00:00
fieldKey === 'solutionsTitleStyle' ||
fieldKey === 'solutionsSubtitle' ||
2026-03-17 13:35:58 +00:00
fieldKey === 'solutionsSubtitleStyle' ||
fieldKey === 'ctaTitle' ||
2026-03-17 13:35:58 +00:00
fieldKey === 'ctaTitleStyle' ||
fieldKey === 'ctaSubtitle' ||
2026-03-17 13:35:58 +00:00
fieldKey === 'ctaSubtitleStyle' ||
fieldKey === 'ctaButtonLabel' ||
fieldKey === 'ctaButtonStyle'
) {
return {
...current,
[fieldKey]: nextValue,
}
}
if (selectedBlockId?.startsWith('slide-')) {
const index = Number(selectedBlockId.replace('slide-', ''))
if (Number.isNaN(index)) {
return current
}
const slides = [...current.slides]
const target = { ...slides[index] }
2026-03-17 13:35:58 +00:00
if (fieldKey === 'title' || fieldKey === 'subtitle' || fieldKey === 'styleClass') {
target[fieldKey] = nextValue
} else if (fieldKey.startsWith('service-')) {
const parts = fieldKey.split('-')
const serviceIndex = Number(parts[1])
2026-03-17 13:35:58 +00:00
const serviceField = parts[2] as 'icon' | 'title' | 'description' | 'styleClass'
const services = [...target.services]
const service = { ...services[serviceIndex] }
service[serviceField] = nextValue
services[serviceIndex] = service
target.services = services
}
slides[index] = target
return {
...current,
slides,
}
}
if (selectedBlockId?.startsWith('feature-')) {
const index = Number(selectedBlockId.replace('feature-', ''))
if (Number.isNaN(index)) {
return current
}
const features = [...current.features]
const feature = { ...features[index] }
2026-03-17 13:35:58 +00:00
const key = fieldKey as 'icon' | 'title' | 'description' | 'styleClass'
feature[key] = nextValue
features[index] = feature
return {
...current,
features,
}
}
if (selectedBlockId?.startsWith('solution-')) {
const index = Number(selectedBlockId.replace('solution-', ''))
if (Number.isNaN(index)) {
return current
}
const solutions = [...current.solutions]
const solution = { ...solutions[index] }
2026-03-17 13:35:58 +00:00
const key = fieldKey as 'icon' | 'title' | 'description' | 'colorClass' | 'styleClass'
solution[key] = nextValue
solutions[index] = solution
return {
...current,
solutions,
}
}
return current
})
}
const selectedSelection: DesignerSelection | null = React.useMemo(() => {
if (!content || !selectedBlockId) {
return null
}
if (selectedBlockId === 'hero') {
return {
id: 'hero',
title: 'Public.home.hero.*',
description: 'Hero arka planini ve CTA metinlerini duzenleyin.',
fields: [
{
key: 'heroBackgroundImage',
label: content.heroBackgroundImageKey,
type: 'image',
value: content.heroBackgroundImage,
},
{
key: 'heroPrimaryCtaLabel',
label: content.heroPrimaryCtaKey,
type: 'text',
value: content.heroPrimaryCtaLabel,
},
2026-03-17 13:35:58 +00:00
{
key: 'heroPrimaryCtaStyle',
label: content.heroPrimaryCtaStyleKey,
type: 'text',
value: content.heroPrimaryCtaStyle,
},
{
key: 'heroSecondaryCtaLabel',
label: content.heroSecondaryCtaKey,
type: 'text',
value: content.heroSecondaryCtaLabel,
},
2026-03-17 13:35:58 +00:00
{
key: 'heroSecondaryCtaStyle',
label: content.heroSecondaryCtaStyleKey,
type: 'text',
value: content.heroSecondaryCtaStyle,
},
],
}
}
if (selectedBlockId.startsWith('slide-')) {
const index = Number(selectedBlockId.replace('slide-', ''))
const slide = content.slides[index]
if (!slide) {
return null
}
return {
id: selectedBlockId,
title: slide.titleKey,
description: 'Slayt baslik, alt baslik ve servis kartlarini duzenleyin.',
fields: [
{
key: 'title',
label: slide.titleKey,
type: 'text',
value: slide.title,
},
{
key: 'subtitle',
label: slide.subtitleKey,
type: 'textarea',
value: slide.subtitle,
},
2026-03-17 13:35:58 +00:00
{
key: 'styleClass',
label: `Slide${index + 1}.StyleClass`,
type: 'text',
value: slide.styleClass,
},
...slide.services.flatMap((service, serviceIndex) => [
{
key: `service-${serviceIndex}-icon`,
label: `Slide${index + 1}.Service${serviceIndex + 1}.Icon`,
type: 'icon' as const,
value: service.icon,
placeholder: 'Ornek: FaUsers',
},
{
key: `service-${serviceIndex}-title`,
label: service.titleKey,
type: 'text' as const,
value: service.title,
},
{
key: `service-${serviceIndex}-description`,
label: service.descriptionKey,
type: 'textarea' as const,
value: service.description,
},
2026-03-17 13:35:58 +00:00
{
key: `service-${serviceIndex}-styleClass`,
label: `Slide${index + 1}.Service${serviceIndex + 1}.StyleClass`,
type: 'text' as const,
value: service.styleClass,
},
]),
],
}
}
if (selectedBlockId === 'features-heading') {
return {
id: selectedBlockId,
title: content.featuresTitleKey,
description: 'Features bolum basliklarini duzenleyin.',
fields: [
{
key: 'featuresTitle',
label: content.featuresTitleKey,
type: 'text',
value: content.featuresTitle,
},
2026-03-17 13:35:58 +00:00
{
key: 'featuresTitleStyle',
label: content.featuresTitleStyleKey,
type: 'text',
value: content.featuresTitleStyle,
},
{
key: 'featuresSubtitle',
label: content.featuresSubtitleKey,
type: 'textarea',
value: content.featuresSubtitle,
},
2026-03-17 13:35:58 +00:00
{
key: 'featuresSubtitleStyle',
label: content.featuresSubtitleStyleKey,
type: 'text',
value: content.featuresSubtitleStyle,
},
],
}
}
if (selectedBlockId.startsWith('feature-')) {
const index = Number(selectedBlockId.replace('feature-', ''))
const item = content.features[index]
if (!item) {
return null
}
return {
id: selectedBlockId,
title: item.titleKey,
description: 'Feature ikon ve metinlerini duzenleyin.',
fields: [
{
key: 'icon',
label: `Feature${index + 1}.Icon`,
type: 'icon',
value: item.icon,
placeholder: 'Ornek: FaChartBar',
},
{
key: 'title',
label: item.titleKey,
type: 'text',
value: item.title,
},
{
key: 'description',
label: item.descriptionKey,
type: 'textarea',
value: item.description,
},
2026-03-17 13:35:58 +00:00
{
key: 'styleClass',
label: `Feature${index + 1}.StyleClass`,
type: 'text',
value: item.styleClass,
},
],
}
}
if (selectedBlockId === 'solutions-heading') {
return {
id: selectedBlockId,
title: content.solutionsTitleKey,
description: 'Solutions bolum basliklarini duzenleyin.',
fields: [
{
key: 'solutionsTitle',
label: content.solutionsTitleKey,
type: 'text',
value: content.solutionsTitle,
},
2026-03-17 13:35:58 +00:00
{
key: 'solutionsTitleStyle',
label: content.solutionsTitleStyleKey,
type: 'text',
value: content.solutionsTitleStyle,
},
{
key: 'solutionsSubtitle',
label: content.solutionsSubtitleKey,
type: 'textarea',
value: content.solutionsSubtitle,
},
2026-03-17 13:35:58 +00:00
{
key: 'solutionsSubtitleStyle',
label: content.solutionsSubtitleStyleKey,
type: 'text',
value: content.solutionsSubtitleStyle,
},
],
}
}
if (selectedBlockId.startsWith('solution-')) {
const index = Number(selectedBlockId.replace('solution-', ''))
const item = content.solutions[index]
if (!item) {
return null
}
return {
id: selectedBlockId,
title: item.titleKey,
description: 'Solution kartini tamamen duzenleyin.',
fields: [
{
key: 'colorClass',
label: `Solution${index + 1}.ColorClass`,
type: 'text',
value: item.colorClass,
},
{
key: 'icon',
label: `Solution${index + 1}.Icon`,
type: 'icon',
value: item.icon,
placeholder: 'Ornek: FaDesktop',
},
{
key: 'title',
label: item.titleKey,
type: 'text',
value: item.title,
},
{
key: 'description',
label: item.descriptionKey,
type: 'textarea',
value: item.description,
},
2026-03-17 13:35:58 +00:00
{
key: 'styleClass',
label: `Solution${index + 1}.StyleClass`,
type: 'text',
value: item.styleClass,
},
],
}
}
if (selectedBlockId === 'cta') {
return {
id: selectedBlockId,
title: content.ctaTitleKey,
description: 'Sayfa alti cta metinlerini duzenleyin.',
fields: [
{
key: 'ctaTitle',
label: content.ctaTitleKey,
type: 'text',
value: content.ctaTitle,
},
2026-03-17 13:35:58 +00:00
{
key: 'ctaTitleStyle',
label: content.ctaTitleStyleKey,
type: 'text',
value: content.ctaTitleStyle,
},
{
key: 'ctaSubtitle',
label: content.ctaSubtitleKey,
type: 'textarea',
value: content.ctaSubtitle,
},
2026-03-17 13:35:58 +00:00
{
key: 'ctaSubtitleStyle',
label: content.ctaSubtitleStyleKey,
type: 'text',
value: content.ctaSubtitleStyle,
},
{
key: 'ctaButtonLabel',
label: content.ctaButtonLabelKey,
type: 'text',
value: content.ctaButtonLabel,
},
2026-03-17 13:35:58 +00:00
{
key: 'ctaButtonStyle',
label: content.ctaButtonStyleKey,
type: 'text',
value: content.ctaButtonStyle,
},
],
}
}
return null
}, [content, selectedBlockId])
const handleSaveAndExit = async () => {
if (!content || isSaving) {
return
}
setIsSaving(true)
try {
await saveHomePage({
cultureName: selectedLanguage,
heroBackgroundImageKey: content.heroBackgroundImageKey,
heroBackgroundImageValue: content.heroBackgroundImage,
heroPrimaryCtaKey: content.heroPrimaryCtaKey,
heroPrimaryCtaValue: content.heroPrimaryCtaLabel,
2026-03-17 13:35:58 +00:00
heroPrimaryCtaStyleKey: content.heroPrimaryCtaStyleKey,
heroPrimaryCtaStyleValue: content.heroPrimaryCtaStyle,
heroSecondaryCtaKey: content.heroSecondaryCtaKey,
heroSecondaryCtaValue: content.heroSecondaryCtaLabel,
2026-03-17 13:35:58 +00:00
heroSecondaryCtaStyleKey: content.heroSecondaryCtaStyleKey,
heroSecondaryCtaStyleValue: content.heroSecondaryCtaStyle,
featuresTitleKey: content.featuresTitleKey,
featuresTitleValue: content.featuresTitle,
2026-03-17 13:35:58 +00:00
featuresTitleStyleKey: content.featuresTitleStyleKey,
featuresTitleStyleValue: content.featuresTitleStyle,
featuresSubtitleKey: content.featuresSubtitleKey,
featuresSubtitleValue: content.featuresSubtitle,
2026-03-17 13:35:58 +00:00
featuresSubtitleStyleKey: content.featuresSubtitleStyleKey,
featuresSubtitleStyleValue: content.featuresSubtitleStyle,
solutionsTitleKey: content.solutionsTitleKey,
solutionsTitleValue: content.solutionsTitle,
2026-03-17 13:35:58 +00:00
solutionsTitleStyleKey: content.solutionsTitleStyleKey,
solutionsTitleStyleValue: content.solutionsTitleStyle,
solutionsSubtitleKey: content.solutionsSubtitleKey,
solutionsSubtitleValue: content.solutionsSubtitle,
2026-03-17 13:35:58 +00:00
solutionsSubtitleStyleKey: content.solutionsSubtitleStyleKey,
solutionsSubtitleStyleValue: content.solutionsSubtitleStyle,
ctaTitleKey: content.ctaTitleKey,
ctaTitleValue: content.ctaTitle,
2026-03-17 13:35:58 +00:00
ctaTitleStyleKey: content.ctaTitleStyleKey,
ctaTitleStyleValue: content.ctaTitleStyle,
ctaSubtitleKey: content.ctaSubtitleKey,
ctaSubtitleValue: content.ctaSubtitle,
2026-03-17 13:35:58 +00:00
ctaSubtitleStyleKey: content.ctaSubtitleStyleKey,
ctaSubtitleStyleValue: content.ctaSubtitleStyle,
ctaButtonLabelKey: content.ctaButtonLabelKey,
ctaButtonLabelValue: content.ctaButtonLabel,
2026-03-17 13:35:58 +00:00
ctaButtonStyleKey: content.ctaButtonStyleKey,
ctaButtonStyleValue: content.ctaButtonStyle,
slides: content.slides.map((slide, slideIndex) => ({
2026-03-17 13:35:58 +00:00
titleKey: slide.titleKey || '',
titleValue: slide.title,
2026-03-17 13:35:58 +00:00
subtitleKey: slide.subtitleKey || '',
subtitleValue: slide.subtitle,
2026-03-17 13:35:58 +00:00
styleClass: slide.styleClass,
services: slide.services.map((service, serviceIndex) => ({
icon: service.icon,
2026-03-17 13:35:58 +00:00
titleKey: service.titleKey || '',
titleValue: service.title,
2026-03-17 13:35:58 +00:00
descriptionKey: service.descriptionKey || '',
descriptionValue: service.description,
2026-03-17 13:35:58 +00:00
styleClass: service.styleClass,
})),
})),
features: content.features.map((feature, index) => ({
icon: feature.icon,
2026-03-17 13:35:58 +00:00
titleKey: feature.titleKey || '',
titleValue: feature.title,
2026-03-17 13:35:58 +00:00
descriptionKey: feature.descriptionKey || '',
descriptionValue: feature.description,
2026-03-17 13:35:58 +00:00
styleClass: feature.styleClass,
})),
solutions: content.solutions.map((solution, index) => ({
icon: solution.icon,
colorClass: solution.colorClass,
2026-03-17 13:35:58 +00:00
titleKey: solution.titleKey || '',
titleValue: solution.title,
2026-03-17 13:35:58 +00:00
descriptionKey: solution.descriptionKey || '',
descriptionValue: solution.description,
2026-03-17 13:35:58 +00:00
styleClass: solution.styleClass,
})),
})
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',
},
)
} catch (error) {
console.error('Home tasarimi kaydedilemedi:', error)
} finally {
setIsSaving(false)
}
}
const handleLanguageChange = (language: string) => {
setLang(language)
}
const handleSelectBlock = (blockId: string) => {
setSelectedBlockId(blockId)
if (!isPanelVisible) {
setIsPanelVisible(true)
}
}
if (loading) {
return (
2026-05-26 16:09:26 +00:00
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-gray-950">
<div className="text-center">
<Loading loading={loading} />
</div>
</div>
)
}
2026-02-24 20:44:16 +00:00
return (
2026-05-26 16:09:26 +00:00
<div className={`min-h-screen bg-gray-50 dark:bg-gray-950 ${isDesignMode && isPanelVisible ? 'xl:pr-[420px]' : ''}`}>
2026-02-24 20:44:16 +00:00
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'App.Home')}
defaultTitle={APP_NAME}
></Helmet>
{isDesignMode && (
<div className="fixed bottom-6 left-6 z-40 rounded-full bg-slate-900 px-4 py-2 text-sm font-medium text-white shadow-xl">
Home designer aktif
</div>
)}
{isDesignMode && !isPanelVisible && (
<Button
type="button"
onClick={() => setIsPanelVisible(true)}
variant="plain"
className="fixed right-4 top-1/2 z-40 -translate-y-1/2 !h-auto !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 Carousel */}
<SelectableBlock
id="hero"
isActive={selectedBlockId === 'hero'}
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
2026-05-26 16:09:26 +00:00
<div className="relative min-h-screen overflow-hidden bg-blue-900 p-20 text-white dark:bg-gray-950">
<div className="absolute inset-0 bg-gradient-to-br from-blue-900 via-indigo-900 to-purple-900 dark:from-gray-950 dark:via-gray-950 dark:to-gray-950"></div>
2026-02-24 20:44:16 +00:00
<div
2026-05-26 16:09:26 +00:00
className="absolute inset-0 opacity-20 dark:opacity-35"
2026-02-24 20:44:16 +00:00
style={{
backgroundImage: `url("${content?.heroBackgroundImage || HOME_HERO_DEFAULT_IMAGE}")`,
2026-02-24 20:44:16 +00:00
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
></div>
2026-05-26 16:09:26 +00:00
<div className="absolute inset-0 bg-blue-950/25 dark:bg-gray-950/45"></div>
2026-02-24 20:44:16 +00:00
{/* Carousel Content */}
<div className="relative container mx-auto px-4 pt-32 pb-16 h-screen flex items-center">
{(content?.slides || []).map((slide, index) => (
2026-02-24 20:44:16 +00:00
<div
key={index}
className={`absolute inset-0 transition-all duration-700 ease-in-out ${
index === currentSlide
? 'opacity-100 translate-x-0'
: index < currentSlide
? 'opacity-0 -translate-x-full'
: 'opacity-0 translate-x-full'
}`}
>
<SelectableBlock
id={`slide-${index}`}
isActive={selectedBlockId === `slide-${index}`}
isDesignMode={isDesignMode}
onSelect={(id) => {
setCurrentSlide(index)
handleSelectBlock(id)
}}
className="h-full"
>
2026-03-17 19:56:34 +00:00
<div className={`container mx-auto px-4 pt-16 ${slide.styleClass || ''}`}>
2026-02-24 20:44:16 +00:00
<div className="max-w-4xl mx-auto text-center">
<h1 className="text-3xl md:text-6xl font-bold mb-6 text-white animate-fade-in">
{slide.title}
</h1>
<p className="text-xl md:text-2xl text-gray-300 mb-12 animate-fade-in-delay">
{slide.subtitle}
</p>
<div className="flex flex-col md:flex-row justify-center gap-6 mb-16">
<button
type="button"
onClick={openDemo}
2026-03-17 13:35:58 +00:00
className={content?.heroPrimaryCtaStyle}
2026-02-24 20:44:16 +00:00
>
{content?.heroPrimaryCtaLabel}{' '}
2026-02-24 20:44:16 +00:00
<FaArrowRight className="ml-2" size={20} />
</button>
2026-02-24 20:44:16 +00:00
<Link
to={ROUTES_ENUM.public.products}
2026-03-17 13:35:58 +00:00
className={content?.heroSecondaryCtaStyle}
2026-02-24 20:44:16 +00:00
>
{content?.heroSecondaryCtaLabel}
2026-02-24 20:44:16 +00:00
</Link>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-4xl mx-auto">
{slide.services.map((service, i) => (
<div
key={i}
2026-03-17 13:35:58 +00:00
className={
service.styleClass ||
'bg-white/5 backdrop-blur-sm rounded-2xl p-8 text-center hover:scale-105 hover:bg-white/10 transition-all'
}
2026-02-24 20:44:16 +00:00
>
{(() => {
const IconComponent = navigationIcon[service.icon || '']
return IconComponent ? (
<IconComponent className="mx-auto mb-4 text-blue-300" size={40} />
) : null
})()}
2026-02-24 20:44:16 +00:00
<h3 className="text-xl font-semibold mb-3 text-white">{service.title}</h3>
<p className="text-gray-300">{service.description}</p>
2026-02-24 20:44:16 +00:00
</div>
))}
</div>
</div>
</div>
</SelectableBlock>
2026-02-24 20:44:16 +00:00
</div>
))}
</div>
{/* Navigation Buttons */}
<Button
2026-02-24 20:44:16 +00:00
onClick={prevSlide}
variant="plain"
shape="circle"
icon={<FaChevronLeft size={24} />}
className="absolute left-4 top-1/2 z-10 -translate-y-1/2 !h-14 !w-14 !px-0 bg-white/10 hover:!bg-white/20 backdrop-blur-sm text-white transition-all"
2026-02-24 20:44:16 +00:00
aria-label="Previous slide"
/>
<Button
2026-02-24 20:44:16 +00:00
onClick={nextSlide}
variant="plain"
shape="circle"
icon={<FaChevronRight size={24} />}
className="absolute right-4 top-1/2 z-10 -translate-y-1/2 !h-14 !w-14 !px-0 bg-white/10 hover:!bg-white/20 backdrop-blur-sm text-white transition-all"
2026-02-24 20:44:16 +00:00
aria-label="Next slide"
/>
2026-02-24 20:44:16 +00:00
{/* Slide Indicators */}
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex gap-3 z-10">
{(content?.slides || []).map((_, index) => (
<Button
2026-02-24 20:44:16 +00:00
key={index}
onClick={() => setCurrentSlide(index)}
variant="plain"
shape="circle"
className={`!h-3 !min-h-3 !px-0 rounded-full transition-all ${
index === currentSlide ? '!w-8 bg-white' : '!w-3 bg-white/50 hover:bg-white/70'
2026-02-24 20:44:16 +00:00
}`}
aria-label={`Go to slide ${index + 1}`}
/>
))}
</div>
{isDesignMode && (
<div className="absolute bottom-24 left-1/2 z-20 -translate-x-1/2 flex gap-2 rounded-full bg-black/30 px-3 py-2 backdrop-blur">
{(content?.slides || []).map((_, index) => (
<Button
key={`editor-slide-${index}`}
type="button"
onClick={() => {
setCurrentSlide(index)
handleSelectBlock(`slide-${index}`)
}}
variant="plain"
className={`!h-auto !rounded-full !px-3 !py-1 text-xs font-semibold text-white transition ${
selectedBlockId === `slide-${index}` ? 'bg-sky-500' : 'bg-white/20 hover:bg-white/30'
}`}
>
Slide {index + 1}
</Button>
))}
</div>
)}
2026-02-24 20:44:16 +00:00
</div>
</SelectableBlock>
2026-02-24 20:44:16 +00:00
{/* Features */}
2026-05-26 16:09:26 +00:00
<section className="py-20 bg-white dark:bg-gray-950">
2026-02-24 20:44:16 +00:00
<div className="container mx-auto px-4">
<SelectableBlock
id="features-heading"
isActive={selectedBlockId === 'features-heading'}
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
2026-02-24 20:44:16 +00:00
<div className="text-center mb-16">
2026-05-26 16:09:26 +00:00
<h2 className={`${content?.featuresTitleStyle || HOME_FEATURES_TITLE_STYLE_DEFAULT} dark:text-gray-100`}>
{content?.featuresTitle}
2026-02-24 20:44:16 +00:00
</h2>
2026-05-26 16:09:26 +00:00
<p className={`${content?.featuresSubtitleStyle || HOME_FEATURES_SUBTITLE_STYLE_DEFAULT} dark:text-gray-300`}>
{content?.featuresSubtitle}
2026-02-24 20:44:16 +00:00
</p>
</div>
</SelectableBlock>
2026-02-24 20:44:16 +00:00
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{(content?.features || []).map((feature, i) => (
<SelectableBlock
2026-02-24 20:44:16 +00:00
key={i}
id={`feature-${i}`}
isActive={selectedBlockId === `feature-${i}`}
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
2026-02-24 20:44:16 +00:00
>
2026-03-17 13:35:58 +00:00
<div
2026-05-26 16:09:26 +00:00
className={`${feature.styleClass || HOME_FEATURE_CARD_STYLE_DEFAULT} dark:bg-gray-900 dark:shadow-gray-950/40`}
2026-03-17 13:35:58 +00:00
>
<div className="mb-6">
{(() => {
const IconComponent = navigationIcon[feature.icon || '']
return IconComponent ? <IconComponent className="w-12 h-12 text-blue-500" /> : null
})()}
</div>
2026-05-26 16:09:26 +00:00
<h2 className="text-2xl font-bold text-gray-900 dark:text-gray-100">{feature.title}</h2>
<p className="text-gray-600 dark:text-gray-300">{feature.description}</p>
2026-02-24 20:44:16 +00:00
</div>
</SelectableBlock>
2026-02-24 20:44:16 +00:00
))}
</div>
</div>
</section>
{/* Solutions */}
2026-05-26 16:09:26 +00:00
<section className="py-20 bg-gray-50 dark:bg-gray-900">
2026-02-24 20:44:16 +00:00
<div className="container mx-auto px-4">
<SelectableBlock
id="solutions-heading"
isActive={selectedBlockId === 'solutions-heading'}
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
2026-02-24 20:44:16 +00:00
<div className="text-center mb-16">
2026-05-26 16:09:26 +00:00
<h2 className={`${content?.solutionsTitleStyle || HOME_SOLUTIONS_TITLE_STYLE_DEFAULT} dark:text-gray-100`}>
{content?.solutionsTitle}
2026-02-24 20:44:16 +00:00
</h2>
2026-05-26 16:09:26 +00:00
<p className={`${content?.solutionsSubtitleStyle || HOME_SOLUTIONS_SUBTITLE_STYLE_DEFAULT} dark:text-gray-300`}>
{content?.solutionsSubtitle}
2026-02-24 20:44:16 +00:00
</p>
</div>
</SelectableBlock>
2026-02-24 20:44:16 +00:00
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{(content?.solutions || []).map((s, i) => (
<SelectableBlock
2026-02-24 20:44:16 +00:00
key={i}
id={`solution-${i}`}
isActive={selectedBlockId === `solution-${i}`}
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
2026-02-24 20:44:16 +00:00
>
2026-03-17 13:35:58 +00:00
<div className={`${s.colorClass} ${s.styleClass || 'p-8 h-full rounded-2xl'}`}>
<div className="mb-6">
{(() => {
const IconComponent = navigationIcon[s.icon || '']
return IconComponent ? <IconComponent className="w-16 h-16 text-white" /> : null
})()}
</div>
2026-02-24 20:44:16 +00:00
<h3 className="text-2xl font-semibold text-white mb-4">{s.title}</h3>
<p className="text-white/90">{s.description}</p>
</div>
</SelectableBlock>
2026-02-24 20:44:16 +00:00
))}
</div>
</div>
</section>
{/* Call to Action */}
<SelectableBlock
id="cta"
isActive={selectedBlockId === 'cta'}
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
2026-06-04 12:08:52 +00:00
<section className="bg-blue-900 py-16 dark:bg-gray-800">
2026-02-24 20:44:16 +00:00
<div className="container mx-auto px-4 text-center">
2026-03-17 13:35:58 +00:00
<h2 className={content?.ctaTitleStyle}>{content?.ctaTitle}</h2>
<p className={content?.ctaSubtitleStyle}>{content?.ctaSubtitle}</p>
2026-02-24 20:44:16 +00:00
<Link
to={ROUTES_ENUM.public.contact}
2026-03-17 13:35:58 +00:00
className={content?.ctaButtonStyle}
2026-02-24 20:44:16 +00:00
>
{content?.ctaButtonLabel}
2026-02-24 20:44:16 +00:00
</Link>
</div>
</section>
</SelectableBlock>
<DesignerDrawer
isOpen={isDesignMode && isPanelVisible}
selection={selectedSelection}
pageTitle="Home"
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 Home