sozsoft-platform/ui/src/views/public/Home.tsx
2026-08-10 16:53:24 +03:00

847 lines
33 KiB
TypeScript

import React, { useEffect, useMemo, useState } from 'react'
import { Link } from 'react-router-dom'
import { FaArrowRight, FaChevronLeft, FaChevronRight } from 'react-icons/fa'
import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { APP_NAME } from '@/constants/app.constant'
import { PUBLIC_PAGE_DESIGN } from '@/constants/permission.constant'
import { Button } from '@/components/ui'
import { useOptionalDemo } from '@/contexts/DemoContext'
import { getHome, HomeDto, saveHomePage } from '@/services/home.service'
import {
DesignerLayer,
DesignerField,
DesignerSelection,
SelectableBlock,
TranslateFn,
iconField,
imageField,
resolveLocalizedValue,
styleField,
textAreaField,
textField,
translateLabel,
usePageDesigner,
} from './designer'
import { designerPageClass } from './pageStyles'
import { PageIcon, PublicPageLoader } from './shared'
interface HomeSlideServiceContent {
icon: string
title: string
titleKey: string
description: string
descriptionKey: string
styleClass: string
}
interface HomeSlideContent {
title: string
titleKey: string
subtitle: string
subtitleKey: string
styleClass: string
services: HomeSlideServiceContent[]
}
interface HomeCardContent {
icon: string
title: string
titleKey: string
description: string
descriptionKey: string
styleClass: string
}
interface HomeSolutionContent extends HomeCardContent {
colorClass: string
}
interface HomeContent {
heroBackgroundImage: string
heroBackgroundImageKey: string
heroPrimaryCtaLabel: string
heroPrimaryCtaKey: string
heroPrimaryCtaStyle: string
heroPrimaryCtaStyleKey: string
heroSecondaryCtaLabel: string
heroSecondaryCtaKey: string
heroSecondaryCtaStyle: string
heroSecondaryCtaStyleKey: string
featuresTitle: string
featuresTitleKey: string
featuresTitleStyle: string
featuresTitleStyleKey: string
featuresSubtitle: string
featuresSubtitleKey: string
featuresSubtitleStyle: string
featuresSubtitleStyleKey: string
solutionsTitle: string
solutionsTitleKey: string
solutionsTitleStyle: string
solutionsTitleStyleKey: string
solutionsSubtitle: string
solutionsSubtitleKey: string
solutionsSubtitleStyle: string
solutionsSubtitleStyleKey: string
ctaTitle: string
ctaTitleKey: string
ctaTitleStyle: string
ctaTitleStyleKey: string
ctaSubtitle: string
ctaSubtitleKey: string
ctaSubtitleStyle: string
ctaSubtitleStyleKey: string
ctaButtonLabel: string
ctaButtonLabelKey: string
ctaButtonStyle: string
ctaButtonStyleKey: string
slides: HomeSlideContent[]
features: HomeCardContent[]
solutions: HomeSolutionContent[]
}
const SLIDE_INTERVAL_MS = 10000
const HOME_HERO_DEFAULT_IMAGE =
'https://images.pexels.com/photos/3183150/pexels-photo-3183150.jpeg?auto=compress&cs=tinysrgb&w=1920'
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 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 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'
const FEATURES_TITLE_STYLE_DEFAULT = 'text-4xl font-bold text-gray-900 dark:text-gray-100 mb-4'
const FEATURES_SUBTITLE_STYLE_DEFAULT = 'text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto'
const SOLUTIONS_TITLE_STYLE_DEFAULT = 'text-4xl font-bold text-gray-900 dark:text-gray-100 mb-4'
const SOLUTIONS_SUBTITLE_STYLE_DEFAULT = 'text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto'
const CTA_TITLE_STYLE_DEFAULT = 'text-3xl font-bold text-white mb-4'
const CTA_SUBTITLE_STYLE_DEFAULT = 'text-white text-lg mb-8'
const CTA_BUTTON_STYLE_DEFAULT =
'inline-block bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-blue-50 transition-colors'
const SLIDE_STYLE_DEFAULT = 'max-w-4xl mx-auto text-center'
const 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 FEATURE_CARD_STYLE_DEFAULT =
'p-8 bg-white dark:bg-gray-900 rounded-xl shadow-lg hover:shadow-xl transition-shadow dark:shadow-gray-950/40'
const SOLUTION_CARD_STYLE_DEFAULT = 'p-8 h-full rounded-2xl'
function buildHomeContent(home: HomeDto | undefined, translate: TranslateFn): HomeContent {
return {
heroBackgroundImage: resolveLocalizedValue(
translate,
home?.heroBackgroundImageKey,
HOME_HERO_DEFAULT_IMAGE,
),
heroBackgroundImageKey: home?.heroBackgroundImageKey || '',
heroPrimaryCtaLabel: resolveLocalizedValue(translate, home?.heroPrimaryCtaKey, ''),
heroPrimaryCtaKey: home?.heroPrimaryCtaKey || '',
heroPrimaryCtaStyle: resolveLocalizedValue(
translate,
HOME_HERO_PRIMARY_CTA_STYLE_KEY,
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,
HERO_SECONDARY_CTA_STYLE_DEFAULT,
),
heroSecondaryCtaStyleKey: HOME_HERO_SECONDARY_CTA_STYLE_KEY,
featuresTitle: resolveLocalizedValue(translate, home?.featuresTitleKey, ''),
featuresTitleKey: home?.featuresTitleKey || '',
featuresTitleStyle: resolveLocalizedValue(
translate,
HOME_FEATURES_TITLE_STYLE_KEY,
FEATURES_TITLE_STYLE_DEFAULT,
),
featuresTitleStyleKey: HOME_FEATURES_TITLE_STYLE_KEY,
featuresSubtitle: resolveLocalizedValue(translate, home?.featuresSubtitleKey, ''),
featuresSubtitleKey: home?.featuresSubtitleKey || '',
featuresSubtitleStyle: resolveLocalizedValue(
translate,
HOME_FEATURES_SUBTITLE_STYLE_KEY,
FEATURES_SUBTITLE_STYLE_DEFAULT,
),
featuresSubtitleStyleKey: HOME_FEATURES_SUBTITLE_STYLE_KEY,
solutionsTitle: resolveLocalizedValue(translate, home?.solutionsTitleKey, ''),
solutionsTitleKey: home?.solutionsTitleKey || '',
solutionsTitleStyle: resolveLocalizedValue(
translate,
HOME_SOLUTIONS_TITLE_STYLE_KEY,
SOLUTIONS_TITLE_STYLE_DEFAULT,
),
solutionsTitleStyleKey: HOME_SOLUTIONS_TITLE_STYLE_KEY,
solutionsSubtitle: resolveLocalizedValue(translate, home?.solutionsSubtitleKey, ''),
solutionsSubtitleKey: home?.solutionsSubtitleKey || '',
solutionsSubtitleStyle: resolveLocalizedValue(
translate,
HOME_SOLUTIONS_SUBTITLE_STYLE_KEY,
SOLUTIONS_SUBTITLE_STYLE_DEFAULT,
),
solutionsSubtitleStyleKey: HOME_SOLUTIONS_SUBTITLE_STYLE_KEY,
ctaTitle: resolveLocalizedValue(translate, home?.ctaTitleKey, ''),
ctaTitleKey: home?.ctaTitleKey || '',
ctaTitleStyle: resolveLocalizedValue(translate, HOME_CTA_TITLE_STYLE_KEY, 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,
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,
CTA_BUTTON_STYLE_DEFAULT,
),
ctaButtonStyleKey: HOME_CTA_BUTTON_STYLE_KEY,
slides: (home?.slidesDto ?? []).map((slide) => ({
title: resolveLocalizedValue(translate, slide.titleKey, slide.titleKey),
titleKey: slide.titleKey || '',
subtitle: resolveLocalizedValue(translate, slide.subtitleKey, slide.subtitleKey),
subtitleKey: slide.subtitleKey || '',
styleClass: slide.styleClass || SLIDE_STYLE_DEFAULT,
services: (slide.services ?? []).map((service) => ({
icon: service.icon || '',
title: resolveLocalizedValue(translate, service.titleKey, service.titleKey),
titleKey: service.titleKey || '',
description: resolveLocalizedValue(translate, service.descriptionKey, service.descriptionKey),
descriptionKey: service.descriptionKey || '',
styleClass: service.styleClass || SLIDE_SERVICE_STYLE_DEFAULT,
})),
})),
features: (home?.featuresDto ?? []).map((feature) => ({
icon: feature.icon || '',
title: resolveLocalizedValue(translate, feature.titleKey, feature.titleKey),
titleKey: feature.titleKey || '',
description: resolveLocalizedValue(translate, feature.descriptionKey, feature.descriptionKey),
descriptionKey: feature.descriptionKey || '',
styleClass: feature.styleClass || FEATURE_CARD_STYLE_DEFAULT,
})),
solutions: (home?.solutionsDto ?? []).map((solution) => ({
icon: solution.icon || '',
colorClass: solution.colorClass || '',
title: resolveLocalizedValue(translate, solution.titleKey, solution.titleKey),
titleKey: solution.titleKey || '',
description: resolveLocalizedValue(translate, solution.descriptionKey, solution.descriptionKey),
descriptionKey: solution.descriptionKey || '',
styleClass: solution.styleClass || SOLUTION_CARD_STYLE_DEFAULT,
})),
}
}
function toSaveInput(content: HomeContent, cultureName: string) {
return {
cultureName,
heroBackgroundImageKey: content.heroBackgroundImageKey,
heroBackgroundImageValue: content.heroBackgroundImage,
heroPrimaryCtaKey: content.heroPrimaryCtaKey,
heroPrimaryCtaValue: content.heroPrimaryCtaLabel,
heroPrimaryCtaStyleKey: content.heroPrimaryCtaStyleKey,
heroPrimaryCtaStyleValue: content.heroPrimaryCtaStyle,
heroSecondaryCtaKey: content.heroSecondaryCtaKey,
heroSecondaryCtaValue: content.heroSecondaryCtaLabel,
heroSecondaryCtaStyleKey: content.heroSecondaryCtaStyleKey,
heroSecondaryCtaStyleValue: content.heroSecondaryCtaStyle,
featuresTitleKey: content.featuresTitleKey,
featuresTitleValue: content.featuresTitle,
featuresTitleStyleKey: content.featuresTitleStyleKey,
featuresTitleStyleValue: content.featuresTitleStyle,
featuresSubtitleKey: content.featuresSubtitleKey,
featuresSubtitleValue: content.featuresSubtitle,
featuresSubtitleStyleKey: content.featuresSubtitleStyleKey,
featuresSubtitleStyleValue: content.featuresSubtitleStyle,
solutionsTitleKey: content.solutionsTitleKey,
solutionsTitleValue: content.solutionsTitle,
solutionsTitleStyleKey: content.solutionsTitleStyleKey,
solutionsTitleStyleValue: content.solutionsTitleStyle,
solutionsSubtitleKey: content.solutionsSubtitleKey,
solutionsSubtitleValue: content.solutionsSubtitle,
solutionsSubtitleStyleKey: content.solutionsSubtitleStyleKey,
solutionsSubtitleStyleValue: content.solutionsSubtitleStyle,
ctaTitleKey: content.ctaTitleKey,
ctaTitleValue: content.ctaTitle,
ctaTitleStyleKey: content.ctaTitleStyleKey,
ctaTitleStyleValue: content.ctaTitleStyle,
ctaSubtitleKey: content.ctaSubtitleKey,
ctaSubtitleValue: content.ctaSubtitle,
ctaSubtitleStyleKey: content.ctaSubtitleStyleKey,
ctaSubtitleStyleValue: content.ctaSubtitleStyle,
ctaButtonLabelKey: content.ctaButtonLabelKey,
ctaButtonLabelValue: content.ctaButtonLabel,
ctaButtonStyleKey: content.ctaButtonStyleKey,
ctaButtonStyleValue: content.ctaButtonStyle,
slides: content.slides.map((slide) => ({
titleKey: slide.titleKey,
titleValue: slide.title,
subtitleKey: slide.subtitleKey,
subtitleValue: slide.subtitle,
styleClass: slide.styleClass,
services: slide.services.map((service) => ({
icon: service.icon,
titleKey: service.titleKey,
titleValue: service.title,
descriptionKey: service.descriptionKey,
descriptionValue: service.description,
styleClass: service.styleClass,
})),
})),
features: content.features.map((feature) => ({
icon: feature.icon,
titleKey: feature.titleKey,
titleValue: feature.title,
descriptionKey: feature.descriptionKey,
descriptionValue: feature.description,
styleClass: feature.styleClass,
})),
solutions: content.solutions.map((solution) => ({
icon: solution.icon,
colorClass: solution.colorClass,
titleKey: solution.titleKey,
titleValue: solution.title,
descriptionKey: solution.descriptionKey,
descriptionValue: solution.description,
styleClass: solution.styleClass,
})),
}
}
function cardFields(path: string, item: HomeCardContent, iconLabel: string): DesignerField[] {
return [
iconField(`${path}.icon`, iconLabel, item.icon, { placeholder: 'Ornek: FaChartBar' }),
textField(`${path}.title`, item.titleKey, item.title),
textAreaField(`${path}.description`, item.descriptionKey, item.description),
styleField(`${path}.styleClass`, `${path}.styleClass`, item.styleClass),
]
}
const Home: React.FC = () => {
const { openDemo } = useOptionalDemo()
const { translate } = useLocalization()
const [home, setHome] = useState<HomeDto>()
const [loading, setLoading] = useState(true)
const [currentSlide, setCurrentSlide] = useState(0)
useEffect(() => {
const fetchHome = async () => {
setLoading(true)
try {
const result = await getHome()
setHome(result.data)
} catch (error) {
console.error('Home alinirken hata olustu:', error)
} finally {
setLoading(false)
}
}
fetchHome()
}, [])
const initialContent = useMemo(
() => (loading ? null : buildHomeContent(home, translate)),
[home, loading, translate],
)
const designer = usePageDesigner<HomeContent>({
pageKey: 'Home',
permission: PUBLIC_PAGE_DESIGN.HOME,
initialContent,
onSave: (content, cultureName) => saveHomePage(toSaveInput(content, cultureName)),
})
const { content, isDesignMode, isPanelVisible, selectedBlockId, selectBlock } = designer
const slides = useMemo(() => content?.slides ?? [], [content?.slides])
const slideCount = slides.length
useEffect(() => {
if (slideCount > 0 && currentSlide >= slideCount) {
setCurrentSlide(0)
}
}, [currentSlide, slideCount])
// Tasarim modunda otomatik gecis kapalidir; aksi halde duzenlenen slayt kayiyor.
useEffect(() => {
if (slideCount < 2 || isDesignMode) {
return
}
const timer = setInterval(
() => setCurrentSlide((previous) => (previous + 1) % slideCount),
SLIDE_INTERVAL_MS,
)
return () => clearInterval(timer)
}, [isDesignMode, slideCount])
const goToSlide = (offset: number) =>
setCurrentSlide((previous) => (previous + offset + Math.max(slideCount, 1)) % Math.max(slideCount, 1))
const selection: DesignerSelection | null = useMemo(() => {
if (!content || !selectedBlockId) {
return null
}
const iconLabel = translateLabel(translate, 'Public.designer.ikonAnahtari', 'Ikon anahtari')
if (selectedBlockId === 'hero') {
return {
id: 'hero',
title: 'Public.home.hero.*',
description: translateLabel(
translate,
'Public.designer.heroHint',
'Hero arka planini ve CTA metinlerini duzenleyin.',
),
fields: [
textField('heroPrimaryCtaLabel', content.heroPrimaryCtaKey, content.heroPrimaryCtaLabel),
textField('heroSecondaryCtaLabel', content.heroSecondaryCtaKey, content.heroSecondaryCtaLabel),
imageField('heroBackgroundImage', content.heroBackgroundImageKey, content.heroBackgroundImage, {
group: 'media',
}),
styleField('heroPrimaryCtaStyle', content.heroPrimaryCtaStyleKey, content.heroPrimaryCtaStyle),
styleField(
'heroSecondaryCtaStyle',
content.heroSecondaryCtaStyleKey,
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 || `Slide ${index + 1}`,
description: translateLabel(
translate,
'Public.designer.slideHint',
'Slayt baslik, alt baslik ve servis kartlarini duzenleyin.',
),
fields: [
textField(`slides.${index}.title`, slide.titleKey, slide.title),
textAreaField(`slides.${index}.subtitle`, slide.subtitleKey, slide.subtitle),
styleField(`slides.${index}.styleClass`, `Slide${index + 1}.StyleClass`, slide.styleClass),
...slide.services.flatMap((service, serviceIndex) => {
const path = `slides.${index}.services.${serviceIndex}`
return [
iconField(`${path}.icon`, `Slide${index + 1}.Service${serviceIndex + 1}.Icon`, service.icon, {
placeholder: 'Ornek: FaUsers',
}),
textField(`${path}.title`, service.titleKey, service.title),
textAreaField(`${path}.description`, service.descriptionKey, service.description),
styleField(
`${path}.styleClass`,
`Slide${index + 1}.Service${serviceIndex + 1}.StyleClass`,
service.styleClass,
),
]
}),
],
}
}
if (selectedBlockId === 'features-heading') {
return {
id: selectedBlockId,
title: content.featuresTitleKey,
description: translateLabel(
translate,
'Public.designer.headingHint',
'Bolum basliklarini duzenleyin.',
),
fields: [
textField('featuresTitle', content.featuresTitleKey, content.featuresTitle),
textAreaField('featuresSubtitle', content.featuresSubtitleKey, content.featuresSubtitle),
styleField('featuresTitleStyle', content.featuresTitleStyleKey, content.featuresTitleStyle),
styleField(
'featuresSubtitleStyle',
content.featuresSubtitleStyleKey,
content.featuresSubtitleStyle,
),
],
}
}
if (selectedBlockId === 'solutions-heading') {
return {
id: selectedBlockId,
title: content.solutionsTitleKey,
description: translateLabel(
translate,
'Public.designer.headingHint',
'Bolum basliklarini duzenleyin.',
),
fields: [
textField('solutionsTitle', content.solutionsTitleKey, content.solutionsTitle),
textAreaField('solutionsSubtitle', content.solutionsSubtitleKey, content.solutionsSubtitle),
styleField('solutionsTitleStyle', content.solutionsTitleStyleKey, content.solutionsTitleStyle),
styleField(
'solutionsSubtitleStyle',
content.solutionsSubtitleStyleKey,
content.solutionsSubtitleStyle,
),
],
}
}
if (selectedBlockId.startsWith('feature-')) {
const index = Number(selectedBlockId.replace('feature-', ''))
const item = content.features[index]
return item
? {
id: selectedBlockId,
title: item.titleKey || `Feature ${index + 1}`,
description: translateLabel(translate, 'Public.designer.desc1', 'Kart icerigini duzenleyin.'),
fields: cardFields(`features.${index}`, item, iconLabel),
}
: null
}
if (selectedBlockId.startsWith('solution-')) {
const index = Number(selectedBlockId.replace('solution-', ''))
const item = content.solutions[index]
return item
? {
id: selectedBlockId,
title: item.titleKey || `Solution ${index + 1}`,
description: translateLabel(translate, 'Public.designer.desc1', 'Kart icerigini duzenleyin.'),
fields: [
...cardFields(`solutions.${index}`, item, iconLabel),
styleField(`solutions.${index}.colorClass`, `Solution${index + 1}.ColorClass`, item.colorClass),
],
}
: null
}
if (selectedBlockId === 'cta') {
return {
id: selectedBlockId,
title: content.ctaTitleKey,
description: translateLabel(
translate,
'Public.designer.desc2',
'Sayfa alti cagri bolumunu duzenleyin.',
),
fields: [
textField('ctaTitle', content.ctaTitleKey, content.ctaTitle),
textAreaField('ctaSubtitle', content.ctaSubtitleKey, content.ctaSubtitle),
textField('ctaButtonLabel', content.ctaButtonLabelKey, content.ctaButtonLabel),
styleField('ctaTitleStyle', content.ctaTitleStyleKey, content.ctaTitleStyle),
styleField('ctaSubtitleStyle', content.ctaSubtitleStyleKey, content.ctaSubtitleStyle),
styleField('ctaButtonStyle', content.ctaButtonStyleKey, content.ctaButtonStyle),
],
}
}
return null
}, [content, selectedBlockId, translate])
if (loading) {
return <PublicPageLoader />
}
return (
<div className={designerPageClass(isDesignMode, isPanelVisible)}>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.Home')}
defaultTitle={APP_NAME}
/>
{/* Hero carousel */}
<div className="relative">
<SelectableBlock
id="hero"
label="Hero"
isActive={selectedBlockId === 'hero'}
isDesignMode={isDesignMode}
onSelect={selectBlock}
>
<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
className="absolute inset-0 opacity-20 dark:opacity-35"
style={{
backgroundImage: `url("${content?.heroBackgroundImage || HOME_HERO_DEFAULT_IMAGE}")`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<div className="absolute inset-0 bg-blue-950/25 dark:bg-gray-950/45" />
<div className="container relative mx-auto flex h-screen items-center px-4 pb-16 pt-32">
{slides.map((slide, index) => (
<div
key={slide.titleKey || index}
aria-hidden={index !== currentSlide}
className={`absolute inset-0 transition-all duration-700 ease-in-out ${
index === currentSlide
? 'opacity-100 translate-x-0'
: index < currentSlide
? 'pointer-events-none opacity-0 -translate-x-full'
: 'pointer-events-none opacity-0 translate-x-full'
}`}
>
<SelectableBlock
id={`slide-${index}`}
label={`Slide ${index + 1}`}
isActive={selectedBlockId === `slide-${index}`}
isDesignMode={isDesignMode}
className="h-full"
onSelect={(id) => {
setCurrentSlide(index)
selectBlock(id)
}}
>
<div className={`container mx-auto px-4 pt-16 ${slide.styleClass}`}>
<div className="mx-auto max-w-4xl text-center">
<h1 className="animate-fade-in mb-6 text-3xl font-bold text-white md:text-6xl">
{slide.title}
</h1>
<p className="animate-fade-in-delay mb-12 text-xl text-gray-300 md:text-2xl">
{slide.subtitle}
</p>
<div className="mb-16 flex flex-col justify-center gap-6 md:flex-row">
<button
type="button"
className={content?.heroPrimaryCtaStyle || HERO_PRIMARY_CTA_STYLE_DEFAULT}
onClick={openDemo}
>
{content?.heroPrimaryCtaLabel}
<FaArrowRight className="ml-2" size={20} />
</button>
<Link
to={ROUTES_ENUM.public.products}
className={content?.heroSecondaryCtaStyle || HERO_SECONDARY_CTA_STYLE_DEFAULT}
>
{content?.heroSecondaryCtaLabel}
</Link>
</div>
<div className="mx-auto grid max-w-4xl grid-cols-1 gap-8 md:grid-cols-3">
{slide.services.map((service, serviceIndex) => (
<div
key={service.titleKey || serviceIndex}
className={service.styleClass || SLIDE_SERVICE_STYLE_DEFAULT}
>
<PageIcon
name={service.icon}
className="mx-auto mb-4 text-blue-300"
size={40}
/>
<h3 className="mb-3 text-xl font-semibold text-white">{service.title}</h3>
<p className="text-gray-300">{service.description}</p>
</div>
))}
</div>
</div>
</div>
</SelectableBlock>
</div>
))}
</div>
{slideCount > 1 && (
<>
<Button
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 text-white backdrop-blur-sm transition-all hover:!bg-white/20"
aria-label="Previous slide"
onClick={() => goToSlide(-1)}
/>
<Button
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 text-white backdrop-blur-sm transition-all hover:!bg-white/20"
aria-label="Next slide"
onClick={() => goToSlide(1)}
/>
<div className="absolute bottom-8 left-1/2 z-10 flex -translate-x-1/2 gap-3">
{slides.map((slide, index) => (
<Button
key={`indicator-${slide.titleKey || 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'
}`}
aria-label={`Go to slide ${index + 1}`}
onClick={() => setCurrentSlide(index)}
/>
))}
</div>
</>
)}
</div>
</SelectableBlock>
{/* Tasarim modunda slayt secici; secilebilir blogun disinda durur ki tiklamalar yutulmasin. */}
{isDesignMode && slideCount > 0 && (
<div className="absolute bottom-24 left-1/2 z-30 flex -translate-x-1/2 gap-2 rounded-full bg-black/40 px-3 py-2 backdrop-blur">
{slides.map((slide, index) => (
<Button
key={`editor-slide-${slide.titleKey || index}`}
type="button"
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'
}`}
onClick={() => {
setCurrentSlide(index)
selectBlock(`slide-${index}`)
}}
>
Slide {index + 1}
</Button>
))}
</div>
)}
</div>
{/* Features */}
<section className="bg-white py-20 dark:bg-gray-950">
<div className="container mx-auto px-4">
<SelectableBlock
id="features-heading"
label="Features"
isActive={selectedBlockId === 'features-heading'}
isDesignMode={isDesignMode}
onSelect={selectBlock}
>
<div className="mb-16 text-center">
<h2 className={content?.featuresTitleStyle || FEATURES_TITLE_STYLE_DEFAULT}>
{content?.featuresTitle}
</h2>
<p className={content?.featuresSubtitleStyle || FEATURES_SUBTITLE_STYLE_DEFAULT}>
{content?.featuresSubtitle}
</p>
</div>
</SelectableBlock>
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-4">
{content?.features.map((feature, index) => (
<SelectableBlock
key={feature.titleKey || index}
id={`feature-${index}`}
label={`Feature ${index + 1}`}
isActive={selectedBlockId === `feature-${index}`}
isDesignMode={isDesignMode}
onSelect={selectBlock}
>
<div className={feature.styleClass || FEATURE_CARD_STYLE_DEFAULT}>
<PageIcon name={feature.icon} className="mb-6 h-12 w-12 text-blue-500" />
<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>
</div>
</SelectableBlock>
))}
</div>
</div>
</section>
{/* Solutions */}
<section className="bg-gray-50 py-20 dark:bg-gray-900">
<div className="container mx-auto px-4">
<SelectableBlock
id="solutions-heading"
label="Solutions"
isActive={selectedBlockId === 'solutions-heading'}
isDesignMode={isDesignMode}
onSelect={selectBlock}
>
<div className="mb-16 text-center">
<h2 className={content?.solutionsTitleStyle || SOLUTIONS_TITLE_STYLE_DEFAULT}>
{content?.solutionsTitle}
</h2>
<p className={content?.solutionsSubtitleStyle || SOLUTIONS_SUBTITLE_STYLE_DEFAULT}>
{content?.solutionsSubtitle}
</p>
</div>
</SelectableBlock>
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-4">
{content?.solutions.map((solution, index) => (
<SelectableBlock
key={solution.titleKey || index}
id={`solution-${index}`}
label={`Solution ${index + 1}`}
isActive={selectedBlockId === `solution-${index}`}
isDesignMode={isDesignMode}
onSelect={selectBlock}
>
<div className={`${solution.colorClass} ${solution.styleClass || SOLUTION_CARD_STYLE_DEFAULT}`}>
<PageIcon name={solution.icon} className="mb-6 h-16 w-16 text-white" />
<h3 className="mb-4 text-2xl font-semibold text-white">{solution.title}</h3>
<p className="text-white/90">{solution.description}</p>
</div>
</SelectableBlock>
))}
</div>
</div>
</section>
{/* CTA */}
<SelectableBlock
id="cta"
label="CTA"
isActive={selectedBlockId === 'cta'}
isDesignMode={isDesignMode}
onSelect={selectBlock}
>
<section className="bg-blue-900 py-16 dark:bg-gray-800">
<div className="container mx-auto px-4 text-center">
<h2 className={content?.ctaTitleStyle || CTA_TITLE_STYLE_DEFAULT}>{content?.ctaTitle}</h2>
<p className={content?.ctaSubtitleStyle || CTA_SUBTITLE_STYLE_DEFAULT}>
{content?.ctaSubtitle}
</p>
<Link
to={ROUTES_ENUM.public.contact}
className={content?.ctaButtonStyle || CTA_BUTTON_STYLE_DEFAULT}
>
{content?.ctaButtonLabel}
</Link>
</div>
</section>
</SelectableBlock>
<DesignerLayer pageTitle="Home" selection={selection} designer={designer} />
</div>
)
}
export default Home