Public Designer için style ve css

This commit is contained in:
Sedat ÖZTÜRK 2026-03-17 16:35:58 +03:00 committed by Sedat Öztürk
parent 900f2815f9
commit ffa3ff2d63
18 changed files with 1124 additions and 286 deletions

View file

@ -66,6 +66,7 @@ public class HomeSlideDto
{
public string TitleKey { get; set; }
public string SubtitleKey { get; set; }
public string StyleClass { get; set; }
public List<HomeSlideServiceDto> Services { get; set; } = [];
}
@ -74,6 +75,7 @@ public class HomeSlideServiceDto
public string Icon { get; set; }
public string TitleKey { get; set; }
public string DescriptionKey { get; set; }
public string StyleClass { get; set; }
}
public class HomeFeatureDto
@ -81,6 +83,7 @@ public class HomeFeatureDto
public string Icon { get; set; }
public string TitleKey { get; set; }
public string DescriptionKey { get; set; }
public string StyleClass { get; set; }
}
public class HomeSolutionDto
@ -89,4 +92,5 @@ public class HomeSolutionDto
public string ColorClass { get; set; }
public string TitleKey { get; set; }
public string DescriptionKey { get; set; }
public string StyleClass { get; set; }
}

View file

@ -15,6 +15,7 @@ public class SaveAboutPageInput
public List<SaveAboutStatInput> Stats { get; set; } = [];
public List<SaveLocalizedTextInput> Descriptions { get; set; } = [];
public List<SaveAboutSectionInput> Sections { get; set; } = [];
public List<SaveLocalizedTextInput> StyleTexts { get; set; } = [];
}
public class SaveAboutStatInput
@ -58,6 +59,7 @@ public class SaveServicesPageInput
public string CtaButtonLabelValue { get; set; }
public List<SaveServiceItemInput> ServiceItems { get; set; } = [];
public List<SaveServiceItemInput> SupportItems { get; set; } = [];
public List<SaveLocalizedTextInput> StyleTexts { get; set; } = [];
}
public class SaveServiceItemInput
@ -84,22 +86,40 @@ public class SaveHomePageInput
public string HeroBackgroundImageValue { get; set; }
public string HeroPrimaryCtaKey { get; set; }
public string HeroPrimaryCtaValue { get; set; }
public string HeroPrimaryCtaStyleKey { get; set; }
public string HeroPrimaryCtaStyleValue { get; set; }
public string HeroSecondaryCtaKey { get; set; }
public string HeroSecondaryCtaValue { get; set; }
public string HeroSecondaryCtaStyleKey { get; set; }
public string HeroSecondaryCtaStyleValue { get; set; }
public string FeaturesTitleKey { get; set; }
public string FeaturesTitleValue { get; set; }
public string FeaturesTitleStyleKey { get; set; }
public string FeaturesTitleStyleValue { get; set; }
public string FeaturesSubtitleKey { get; set; }
public string FeaturesSubtitleValue { get; set; }
public string FeaturesSubtitleStyleKey { get; set; }
public string FeaturesSubtitleStyleValue { get; set; }
public string SolutionsTitleKey { get; set; }
public string SolutionsTitleValue { get; set; }
public string SolutionsTitleStyleKey { get; set; }
public string SolutionsTitleStyleValue { get; set; }
public string SolutionsSubtitleKey { get; set; }
public string SolutionsSubtitleValue { get; set; }
public string SolutionsSubtitleStyleKey { get; set; }
public string SolutionsSubtitleStyleValue { get; set; }
public string CtaTitleKey { get; set; }
public string CtaTitleValue { get; set; }
public string CtaTitleStyleKey { get; set; }
public string CtaTitleStyleValue { get; set; }
public string CtaSubtitleKey { get; set; }
public string CtaSubtitleValue { get; set; }
public string CtaSubtitleStyleKey { get; set; }
public string CtaSubtitleStyleValue { get; set; }
public string CtaButtonLabelKey { get; set; }
public string CtaButtonLabelValue { get; set; }
public string CtaButtonStyleKey { get; set; }
public string CtaButtonStyleValue { get; set; }
public List<SaveHomeSlideInput> Slides { get; set; } = [];
public List<SaveHomeFeatureInput> Features { get; set; } = [];
public List<SaveHomeSolutionInput> Solutions { get; set; } = [];
@ -111,6 +131,7 @@ public class SaveHomeSlideInput
public string TitleValue { get; set; }
public string SubtitleKey { get; set; }
public string SubtitleValue { get; set; }
public string StyleClass { get; set; }
public List<SaveHomeSlideServiceInput> Services { get; set; } = [];
}
@ -121,6 +142,7 @@ public class SaveHomeSlideServiceInput
public string TitleValue { get; set; }
public string DescriptionKey { get; set; }
public string DescriptionValue { get; set; }
public string StyleClass { get; set; }
}
public class SaveHomeFeatureInput
@ -130,6 +152,7 @@ public class SaveHomeFeatureInput
public string TitleValue { get; set; }
public string DescriptionKey { get; set; }
public string DescriptionValue { get; set; }
public string StyleClass { get; set; }
}
public class SaveHomeSolutionInput
@ -140,6 +163,7 @@ public class SaveHomeSolutionInput
public string TitleValue { get; set; }
public string DescriptionKey { get; set; }
public string DescriptionValue { get; set; }
public string StyleClass { get; set; }
}
public class SaveContactPageInput
@ -168,6 +192,7 @@ public class SaveContactPageInput
public string BankBranch { get; set; }
public string BankAccountNumber { get; set; }
public string BankIban { get; set; }
public string BankStyleClass { get; set; }
public string WorkHoursTitleKey { get; set; }
public string WorkHoursTitleValue { get; set; }
@ -177,6 +202,7 @@ public class SaveContactPageInput
public string WorkWeekendValue { get; set; }
public string WorkWhatsappKey { get; set; }
public string WorkWhatsappValue { get; set; }
public string WorkHoursStyleClass { get; set; }
public string MapTitleKey { get; set; }
public string MapTitleValue { get; set; }
@ -186,4 +212,7 @@ public class SaveContactPageInput
public bool MapAllowFullScreen { get; set; }
public string MapLoading { get; set; }
public string MapReferrerPolicy { get; set; }
public string MapContainerStyleClass { get; set; }
public string MapFrameStyleClass { get; set; }
public List<SaveLocalizedTextInput> StyleTexts { get; set; } = [];
}

View file

@ -128,6 +128,11 @@ public class PublicAppService : PlatformAppService
await UpsertLanguageTextAsync(input.CultureName, section.DescriptionKey, section.DescriptionValue);
}
foreach (var styleText in input.StyleTexts)
{
await UpsertLanguageTextAsync(input.CultureName, styleText.Key, styleText.Value);
}
await _aboutRepository.UpdateAsync(entity, autoSave: true);
await _languageTextAppService.ClearRedisCacheAsync();
}
@ -176,6 +181,11 @@ public class PublicAppService : PlatformAppService
await UpsertLanguageTextAsync(input.CultureName, input.CtaDescriptionKey, input.CtaDescriptionValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue);
foreach (var styleText in input.StyleTexts)
{
await UpsertLanguageTextAsync(input.CultureName, styleText.Key, styleText.Value);
}
await CurrentUnitOfWork!.SaveChangesAsync();
await _languageTextAppService.ClearRedisCacheAsync();
}
@ -212,11 +222,13 @@ public class PublicAppService : PlatformAppService
{
TitleKey = slide.TitleKey,
SubtitleKey = slide.SubtitleKey,
StyleClass = slide.StyleClass,
Services = slide.Services.Select(service => new HomeSlideServiceDto
{
Icon = service.Icon,
TitleKey = service.TitleKey,
DescriptionKey = service.DescriptionKey,
StyleClass = service.StyleClass,
}).ToList(),
}).ToList());
@ -225,6 +237,7 @@ public class PublicAppService : PlatformAppService
Icon = feature.Icon,
TitleKey = feature.TitleKey,
DescriptionKey = feature.DescriptionKey,
StyleClass = feature.StyleClass,
}).ToList());
entity.SolutionsJson = JsonSerializer.Serialize(input.Solutions.Select(solution => new HomeSolutionDto
@ -233,6 +246,7 @@ public class PublicAppService : PlatformAppService
ColorClass = solution.ColorClass,
TitleKey = solution.TitleKey,
DescriptionKey = solution.DescriptionKey,
StyleClass = solution.StyleClass,
}).ToList());
if (isNewEntity)
@ -246,14 +260,23 @@ public class PublicAppService : PlatformAppService
await UpsertLanguageTextAsync(input.CultureName, input.HeroBackgroundImageKey, input.HeroBackgroundImageValue);
await UpsertLanguageTextAsync(input.CultureName, input.HeroPrimaryCtaKey, input.HeroPrimaryCtaValue);
await UpsertLanguageTextAsync(input.CultureName, input.HeroPrimaryCtaStyleKey, input.HeroPrimaryCtaStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaKey, input.HeroSecondaryCtaValue);
await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaStyleKey, input.HeroSecondaryCtaStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleKey, input.FeaturesTitleValue);
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleStyleKey, input.FeaturesTitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleKey, input.FeaturesSubtitleValue);
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleStyleKey, input.FeaturesSubtitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleKey, input.SolutionsTitleValue);
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleStyleKey, input.SolutionsTitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleKey, input.SolutionsSubtitleValue);
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleStyleKey, input.SolutionsSubtitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleKey, input.CtaTitleValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleStyleKey, input.CtaTitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleKey, input.CtaSubtitleValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleStyleKey, input.CtaSubtitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonStyleKey, input.CtaButtonStyleValue);
foreach (var slide in input.Slides)
{
@ -528,6 +551,7 @@ public class PublicAppService : PlatformAppService
Branch = input.BankBranch,
AccountNumber = input.BankAccountNumber,
Iban = input.BankIban,
StyleClass = input.BankStyleClass,
});
entity.WorkHoursJson = JsonSerializer.Serialize(new WorkHoursDto
@ -535,6 +559,7 @@ public class PublicAppService : PlatformAppService
Weekday = input.WorkWeekdayKey,
Weekend = input.WorkWeekendKey,
Whatsapp = input.WorkWhatsappKey,
StyleClass = input.WorkHoursStyleClass,
});
entity.MapJson = JsonSerializer.Serialize(new MapDto
@ -546,6 +571,8 @@ public class PublicAppService : PlatformAppService
AllowFullScreen = input.MapAllowFullScreen,
Loading = input.MapLoading,
ReferrerPolicy = input.MapReferrerPolicy,
ContainerStyleClass = input.MapContainerStyleClass,
FrameStyleClass = input.MapFrameStyleClass,
});
await _contactRepository.UpdateAsync(entity, autoSave: false);
@ -562,6 +589,11 @@ public class PublicAppService : PlatformAppService
await UpsertLanguageTextAsync(input.CultureName, input.WorkWhatsappKey, input.WorkWhatsappValue);
await UpsertLanguageTextAsync(input.CultureName, input.MapTitleKey, input.MapTitleValue);
foreach (var styleText in input.StyleTexts)
{
await UpsertLanguageTextAsync(input.CultureName, styleText.Key, styleText.Value);
}
await CurrentUnitOfWork!.SaveChangesAsync();
await _languageTextAppService.ClearRedisCacheAsync();
}

View file

@ -6,4 +6,5 @@ public class BankDto
public string Branch { get; set; }
public string AccountNumber { get; set; }
public string Iban { get; set; }
public string StyleClass { get; set; }
}

View file

@ -7,6 +7,8 @@ public class MapDto
public string Width { get; set; }
public string Height { get; set; }
public string Style { get; set; }
public string ContainerStyleClass { get; set; }
public string FrameStyleClass { get; set; }
public bool? AllowFullScreen { get; set; }
public string Loading { get; set; }
public string ReferrerPolicy { get; set; }

View file

@ -5,4 +5,5 @@ public class WorkHoursDto
public string Weekday { get; set; }
public string Weekend { get; set; }
public string Whatsapp { get; set; }
public string StyleClass { get; set; }
}

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260317104139_Initial")]
[Migration("20260317120000_Initial")]
partial class Initial
{
/// <inheritdoc />

View file

@ -15,12 +15,14 @@ export interface BankDto {
branch: string
accountNumber: string
iban: string
styleClass?: string
}
export interface WorkHoursDto {
weekday: string
weekend: string
whatsapp: string
styleClass?: string
}
export interface MapDto {
@ -28,6 +30,9 @@ export interface MapDto {
src: string
width: string
height: string
style?: string
containerStyleClass?: string
frameStyleClass?: string
allowFullScreen?: boolean
loading: string
referrerPolicy: string

View file

@ -35,6 +35,7 @@ export interface SaveAboutPageInput {
stats: SaveAboutStatInput[]
descriptions: SaveLocalizedTextInput[]
sections: SaveAboutSectionInput[]
styleTexts: SaveLocalizedTextInput[]
}
export function getAbout() {

View file

@ -1,6 +1,11 @@
import apiService from './api.service'
import { ContactDto } from '@/proxy/contact/models'
export interface SaveLocalizedTextInput {
key: string
value: string
}
export function getContact() {
return apiService.fetchData<ContactDto>(
{
@ -33,6 +38,7 @@ export interface SaveContactPageInput extends Record<string, unknown> {
bankBranch: string
bankAccountNumber: string
bankIban: string
bankStyleClass: string
workHoursTitleKey: string
workHoursTitleValue: string
workWeekdayKey: string
@ -41,6 +47,7 @@ export interface SaveContactPageInput extends Record<string, unknown> {
workWeekendValue: string
workWhatsappKey: string
workWhatsappValue: string
workHoursStyleClass: string
mapTitleKey: string
mapTitleValue: string
mapSrc: string
@ -49,6 +56,9 @@ export interface SaveContactPageInput extends Record<string, unknown> {
mapAllowFullScreen: boolean
mapLoading: string
mapReferrerPolicy: string
mapContainerStyleClass: string
mapFrameStyleClass: string
styleTexts: SaveLocalizedTextInput[]
}
export function saveContactPage(input: SaveContactPageInput) {

View file

@ -4,11 +4,13 @@ export interface HomeSlideServiceDto {
icon: string
titleKey: string
descriptionKey: string
styleClass?: string
}
export interface HomeSlideDto {
titleKey: string
subtitleKey: string
styleClass?: string
services: HomeSlideServiceDto[]
}
@ -16,6 +18,7 @@ export interface HomeFeatureDto {
icon: string
titleKey: string
descriptionKey: string
styleClass?: string
}
export interface HomeSolutionDto {
@ -23,6 +26,7 @@ export interface HomeSolutionDto {
colorClass: string
titleKey: string
descriptionKey: string
styleClass?: string
}
export interface HomeDto {
@ -48,6 +52,7 @@ export interface SaveHomeSlideServiceInput {
titleValue: string
descriptionKey: string
descriptionValue: string
styleClass: string
}
export interface SaveHomeSlideInput {
@ -55,6 +60,7 @@ export interface SaveHomeSlideInput {
titleValue: string
subtitleKey: string
subtitleValue: string
styleClass: string
services: SaveHomeSlideServiceInput[]
}
@ -64,6 +70,7 @@ export interface SaveHomeFeatureInput {
titleValue: string
descriptionKey: string
descriptionValue: string
styleClass: string
}
export interface SaveHomeSolutionInput {
@ -73,6 +80,7 @@ export interface SaveHomeSolutionInput {
titleValue: string
descriptionKey: string
descriptionValue: string
styleClass: string
}
export interface SaveHomePageInput {
@ -81,22 +89,40 @@ export interface SaveHomePageInput {
heroBackgroundImageValue: string
heroPrimaryCtaKey: string
heroPrimaryCtaValue: string
heroPrimaryCtaStyleKey: string
heroPrimaryCtaStyleValue: string
heroSecondaryCtaKey: string
heroSecondaryCtaValue: string
heroSecondaryCtaStyleKey: string
heroSecondaryCtaStyleValue: string
featuresTitleKey: string
featuresTitleValue: string
featuresTitleStyleKey: string
featuresTitleStyleValue: string
featuresSubtitleKey: string
featuresSubtitleValue: string
featuresSubtitleStyleKey: string
featuresSubtitleStyleValue: string
solutionsTitleKey: string
solutionsTitleValue: string
solutionsTitleStyleKey: string
solutionsTitleStyleValue: string
solutionsSubtitleKey: string
solutionsSubtitleValue: string
solutionsSubtitleStyleKey: string
solutionsSubtitleStyleValue: string
ctaTitleKey: string
ctaTitleValue: string
ctaTitleStyleKey: string
ctaTitleStyleValue: string
ctaSubtitleKey: string
ctaSubtitleValue: string
ctaSubtitleStyleKey: string
ctaSubtitleStyleValue: string
ctaButtonLabelKey: string
ctaButtonLabelValue: string
ctaButtonStyleKey: string
ctaButtonStyleValue: string
slides: SaveHomeSlideInput[]
features: SaveHomeFeatureInput[]
solutions: SaveHomeSolutionInput[]

View file

@ -36,6 +36,7 @@ export interface SaveServicesPageInput {
ctaButtonLabelValue: string
serviceItems: SaveServiceItemInput[]
supportItems: SaveServiceItemInput[]
styleTexts: SaveLocalizedTextInput[]
}
export function getServices() {

View file

@ -9,7 +9,6 @@ import { APP_NAME } from '@/constants/app.constant'
import { Notification, toast } from '@/components/ui'
import { useStoreState } from '@/store'
import { useStoreActions } from '@/store'
import { useNavigate } from 'react-router-dom'
import DesignerDrawer from './designer/DesignerDrawer'
import SelectableBlock from './designer/SelectableBlock'
import { DesignerSelection } from './designer/types'
@ -20,6 +19,12 @@ interface AboutStatContent {
value: string
label: string
labelKey: string
styleClassKey: string
styleClass: string
valueStyleClassKey: string
valueStyleClass: string
labelStyleClassKey: string
labelStyleClass: string
useCounter?: boolean
counterEnd?: string
counterSuffix?: string
@ -29,6 +34,8 @@ interface AboutStatContent {
interface AboutDescriptionContent {
key: string
text: string
styleClassKey: string
styleClass: string
}
interface AboutSectionContent {
@ -36,15 +43,29 @@ interface AboutSectionContent {
description: string
titleKey: string
descriptionKey: string
cardStyleClassKey: string
cardStyleClass: string
titleStyleClassKey: string
titleStyleClass: string
descriptionStyleClassKey: string
descriptionStyleClass: string
}
interface AboutContent {
heroTitle: string
heroTitleKey: string
heroTitleStyleClassKey: string
heroTitleStyleClass: string
heroSubtitle: string
heroSubtitleKey: string
heroSubtitleStyleClassKey: string
heroSubtitleStyleClass: string
heroImage: string
heroImageKey: string
heroSectionStyleClassKey: string
heroSectionStyleClass: string
descriptionsContainerStyleClassKey: string
descriptionsContainerStyleClass: string
stats: AboutStatContent[]
descriptions: AboutDescriptionContent[]
sections: AboutSectionContent[]
@ -55,6 +76,10 @@ const ABOUT_HERO_IMAGE =
const ABOUT_HERO_TITLE_KEY = 'App.About'
const ABOUT_HERO_SUBTITLE_KEY = 'Public.about.subtitle'
const ABOUT_HERO_IMAGE_KEY = 'Public.about.heroImage'
const ABOUT_HERO_SECTION_STYLE_KEY = 'Public.about.hero.sectionStyleClass'
const ABOUT_HERO_TITLE_STYLE_KEY = 'Public.about.hero.titleStyleClass'
const ABOUT_HERO_SUBTITLE_STYLE_KEY = 'Public.about.hero.subtitleStyleClass'
const ABOUT_DESCRIPTIONS_CONTAINER_STYLE_KEY = 'Public.about.descriptions.containerStyleClass'
function isLikelyLocalizationKey(value?: string) {
return Boolean(value && /^[A-Za-z0-9_.-]+$/.test(value) && value.includes('.'))
@ -84,18 +109,60 @@ function buildAboutContent(
return {
heroTitle: resolveLocalizedValue(translate, ABOUT_HERO_TITLE_KEY, 'About'),
heroTitleKey: ABOUT_HERO_TITLE_KEY,
heroTitleStyleClassKey: ABOUT_HERO_TITLE_STYLE_KEY,
heroTitleStyleClass: resolveLocalizedValue(
translate,
ABOUT_HERO_TITLE_STYLE_KEY,
'text-5xl font-bold ml-4 mt-3 mb-2 text-white',
),
heroSubtitle: resolveLocalizedValue(translate, ABOUT_HERO_SUBTITLE_KEY),
heroSubtitleKey: ABOUT_HERO_SUBTITLE_KEY,
heroSubtitleStyleClassKey: ABOUT_HERO_SUBTITLE_STYLE_KEY,
heroSubtitleStyleClass: resolveLocalizedValue(
translate,
ABOUT_HERO_SUBTITLE_STYLE_KEY,
'text-xl max-w-3xl ml-4',
),
heroImage: resolveLocalizedValue(translate, ABOUT_HERO_IMAGE_KEY, ABOUT_HERO_IMAGE),
heroImageKey: ABOUT_HERO_IMAGE_KEY,
heroSectionStyleClassKey: ABOUT_HERO_SECTION_STYLE_KEY,
heroSectionStyleClass: resolveLocalizedValue(
translate,
ABOUT_HERO_SECTION_STYLE_KEY,
'relative bg-blue-900 text-white py-12',
),
descriptionsContainerStyleClassKey: ABOUT_DESCRIPTIONS_CONTAINER_STYLE_KEY,
descriptionsContainerStyleClass: resolveLocalizedValue(
translate,
ABOUT_DESCRIPTIONS_CONTAINER_STYLE_KEY,
'p-5 mx-auto text-gray-800 text-lg leading-relaxed shadow-md bg-white border-l-4 border-blue-600',
),
stats:
about?.statsDto.map((stat) => ({
about?.statsDto.map((stat, index) => ({
styleClassKey: `Public.about.dynamic.stat.${index + 1}.styleClass`,
styleClass: resolveLocalizedValue(
translate,
`Public.about.dynamic.stat.${index + 1}.styleClass`,
'text-center rounded-xl px-4 py-6',
),
valueStyleClassKey: `Public.about.dynamic.stat.${index + 1}.valueStyleClass`,
valueStyleClass: resolveLocalizedValue(
translate,
`Public.about.dynamic.stat.${index + 1}.valueStyleClass`,
'text-4xl font-bold text-gray-900 mb-2',
),
labelStyleClassKey: `Public.about.dynamic.stat.${index + 1}.labelStyleClass`,
labelStyleClass: resolveLocalizedValue(
translate,
`Public.about.dynamic.stat.${index + 1}.labelStyleClass`,
'text-gray-600',
),
icon: stat.icon || '',
value: stat.value,
label: resolveLocalizedValue(translate, stat.labelKey, stat.labelKey),
labelKey:
(isLikelyLocalizationKey(stat.labelKey) ? stat.labelKey : undefined) ||
`Public.about.dynamic.stat.${stat.value}.label`,
`Public.about.dynamic.stat.${index + 1}.label`,
useCounter: stat.useCounter,
counterEnd: stat.counterEnd,
counterSuffix: stat.counterSuffix,
@ -107,6 +174,12 @@ function buildAboutContent(
(isLikelyLocalizationKey(item) ? item : undefined) ||
`Public.about.dynamic.description.${index + 1}`,
text: resolveLocalizedValue(translate, item, item),
styleClassKey: `Public.about.dynamic.description.${index + 1}.styleClass`,
styleClass: resolveLocalizedValue(
translate,
`Public.about.dynamic.description.${index + 1}.styleClass`,
index % 2 === 0 ? '' : 'text-center p-5 text-blue-800',
),
})) ?? [],
sections:
about?.sectionsDto.map((section) => ({
@ -118,13 +191,30 @@ function buildAboutContent(
descriptionKey:
(isLikelyLocalizationKey(section.descKey) ? section.descKey : undefined) ||
`Public.about.dynamic.section.${section.key}.description`,
cardStyleClassKey: `Public.about.dynamic.section.${section.key}.cardStyleClass`,
cardStyleClass: resolveLocalizedValue(
translate,
`Public.about.dynamic.section.${section.key}.cardStyleClass`,
'bg-white p-8 rounded-xl shadow-lg',
),
titleStyleClassKey: `Public.about.dynamic.section.${section.key}.titleStyleClass`,
titleStyleClass: resolveLocalizedValue(
translate,
`Public.about.dynamic.section.${section.key}.titleStyleClass`,
'text-2xl font-bold text-gray-900 mb-4',
),
descriptionStyleClassKey: `Public.about.dynamic.section.${section.key}.descriptionStyleClass`,
descriptionStyleClass: resolveLocalizedValue(
translate,
`Public.about.dynamic.section.${section.key}.descriptionStyleClass`,
'text-gray-700',
),
})) ?? [],
}
}
const About: React.FC = () => {
const { translate } = useLocalization()
const navigate = useNavigate()
const { setLang } = useStoreActions((actions) => actions.locale)
const { getConfig } = useStoreActions((actions) => actions.abpConfig)
const configCultureName = useStoreState(
@ -207,7 +297,15 @@ const About: React.FC = () => {
const handleFieldChange = (fieldKey: string, value: string | string[]) => {
updateContent((current) => {
if (fieldKey === 'heroTitle' || fieldKey === 'heroSubtitle' || fieldKey === 'heroImage') {
if (
fieldKey === 'heroTitle' ||
fieldKey === 'heroSubtitle' ||
fieldKey === 'heroImage' ||
fieldKey === 'heroSectionStyleClass' ||
fieldKey === 'heroTitleStyleClass' ||
fieldKey === 'heroSubtitleStyleClass' ||
fieldKey === 'descriptionsContainerStyleClass'
) {
return {
...current,
[fieldKey]: value as string,
@ -228,6 +326,20 @@ const About: React.FC = () => {
}
}
if (fieldKey.startsWith('descriptionStyle-')) {
const index = Number(fieldKey.replace('descriptionStyle-', ''))
const descriptions = [...current.descriptions]
descriptions[index] = {
...descriptions[index],
styleClass: value as string,
}
return {
...current,
descriptions,
}
}
if (selectedBlockId?.startsWith('stat-')) {
const index = Number(selectedBlockId.replace('stat-', ''))
const stats = [...current.stats]
@ -277,18 +389,36 @@ const About: React.FC = () => {
type: 'text',
value: content.heroTitle,
},
{
key: 'heroTitleStyleClass',
label: content.heroTitleStyleClassKey,
type: 'text',
value: content.heroTitleStyleClass,
},
{
key: 'heroSubtitle',
label: content.heroSubtitleKey,
type: 'textarea',
value: content.heroSubtitle,
},
{
key: 'heroSubtitleStyleClass',
label: content.heroSubtitleStyleClassKey,
type: 'text',
value: content.heroSubtitleStyleClass,
},
{
key: 'heroImage',
label: content.heroImageKey,
type: 'image',
value: content.heroImage,
},
{
key: 'heroSectionStyleClass',
label: content.heroSectionStyleClassKey,
type: 'text',
value: content.heroSectionStyleClass,
},
],
}
}
@ -298,13 +428,29 @@ const About: React.FC = () => {
id: 'descriptions',
title: 'Public.about.description.*',
description: 'Orta bolumdeki aciklama metinlerini duzenleyin.',
fields: content.descriptions.map((item, index) => ({
key: `description-${index}`,
label: item.key || `Public.about.dynamic.description.${index + 1}`,
type: 'textarea',
value: item.text,
rows: index % 2 === 0 ? 4 : 3,
})),
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,
},
]),
],
}
}
@ -340,6 +486,24 @@ const About: React.FC = () => {
type: 'text',
value: stat.label,
},
{
key: 'styleClass',
label: stat.styleClassKey,
type: 'text',
value: stat.styleClass,
},
{
key: 'valueStyleClass',
label: stat.valueStyleClassKey,
type: 'text',
value: stat.valueStyleClass,
},
{
key: 'labelStyleClass',
label: stat.labelStyleClassKey,
type: 'text',
value: stat.labelStyleClass,
},
],
}
}
@ -369,6 +533,24 @@ const About: React.FC = () => {
type: 'textarea',
value: section.description,
},
{
key: 'cardStyleClass',
label: section.cardStyleClassKey,
type: 'text',
value: section.cardStyleClass,
},
{
key: 'titleStyleClass',
label: section.titleStyleClassKey,
type: 'text',
value: section.titleStyleClass,
},
{
key: 'descriptionStyleClass',
label: section.descriptionStyleClassKey,
type: 'text',
value: section.descriptionStyleClass,
},
],
}
}
@ -413,6 +595,56 @@ const About: React.FC = () => {
section.descriptionKey || `Public.about.dynamic.section.${index + 1}.description`,
descriptionValue: section.description,
})),
styleTexts: [
{
key: content.heroSectionStyleClassKey,
value: content.heroSectionStyleClass,
},
{
key: content.heroTitleStyleClassKey,
value: content.heroTitleStyleClass,
},
{
key: content.heroSubtitleStyleClassKey,
value: content.heroSubtitleStyleClass,
},
{
key: content.descriptionsContainerStyleClassKey,
value: content.descriptionsContainerStyleClass,
},
...content.stats.flatMap((stat) => [
{
key: stat.styleClassKey,
value: stat.styleClass,
},
{
key: stat.valueStyleClassKey,
value: stat.valueStyleClass,
},
{
key: stat.labelStyleClassKey,
value: stat.labelStyleClass,
},
]),
...content.descriptions.map((item) => ({
key: item.styleClassKey,
value: item.styleClass,
})),
...content.sections.flatMap((section) => [
{
key: section.cardStyleClassKey,
value: section.cardStyleClass,
},
{
key: section.titleStyleClassKey,
value: section.titleStyleClass,
},
{
key: section.descriptionStyleClassKey,
value: section.descriptionStyleClass,
},
]),
],
})
await getConfig(false)
@ -483,7 +715,7 @@ const About: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="relative bg-blue-900 text-white py-12">
<div className={content?.heroSectionStyleClass || 'relative bg-blue-900 text-white py-12'}>
<div
className="absolute inset-0 opacity-20"
style={{
@ -493,10 +725,10 @@ const About: React.FC = () => {
}}
></div>
<div className="container mx-auto pt-20 relative">
<h1 className="text-5xl font-bold ml-4 mt-3 mb-2 text-white">
<h1 className={content?.heroTitleStyleClass || 'text-5xl font-bold ml-4 mt-3 mb-2 text-white'}>
{content?.heroTitle}
</h1>
<p className="text-xl max-w-3xl ml-4">{content?.heroSubtitle}</p>
<p className={content?.heroSubtitleStyleClass || 'text-xl max-w-3xl ml-4'}>{content?.heroSubtitle}</p>
</div>
</div>
</SelectableBlock>
@ -516,14 +748,14 @@ const About: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="text-center rounded-xl px-4 py-6">
<div className={stat.styleClass || 'text-center rounded-xl px-4 py-6'}>
{IconComponent && (
<IconComponent
className={`w-12 h-12 mx-auto mb-4 ${getIconColor(index)}`}
/>
)}
<div className="text-4xl font-bold text-gray-900 mb-2">{stat.value}</div>
<div className="text-gray-600">{stat.label}</div>
<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>
</div>
</SelectableBlock>
)
@ -542,11 +774,12 @@ const About: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="p-5 mx-auto mx-auto text-gray-800 text-lg leading-relaxed shadow-md bg-white border-l-4 border-blue-600">
<p>{content?.descriptions[0]?.text}</p>
<p className="text-center p-5 text-blue-800">{content?.descriptions[1]?.text}</p>
<p>{content?.descriptions[2]?.text}</p>
<p className="text-center p-5 text-blue-800">{content?.descriptions[3]?.text}</p>
<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>
))}
</div>
</SelectableBlock>
</div>
@ -560,9 +793,9 @@ const About: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="bg-white p-8 rounded-xl shadow-lg">
<h3 className="text-2xl font-bold text-gray-900 mb-4">{section.title}</h3>
<p className="text-gray-700">{section.description}</p>
<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>
</div>
</SelectableBlock>
))}

View file

@ -25,12 +25,22 @@ import { Notification, toast } from '@/components/ui'
interface ContactContent {
heroTitle: string
heroTitleKey: string
heroSectionStyleClass: string
heroSectionStyleClassKey: string
heroTitleStyleClass: string
heroTitleStyleClassKey: string
heroSubtitle: string
heroSubtitleKey: string
heroSubtitleStyleClass: string
heroSubtitleStyleClassKey: string
heroImage: string
heroImageKey: string
contactInfoTitle: string
contactInfoTitleKey: string
contactInfoCardStyleClass: string
contactInfoCardStyleClassKey: string
contactInfoTitleStyleClass: string
contactInfoTitleStyleClassKey: string
address: string
addressKey: string
phoneNumber: string
@ -43,6 +53,7 @@ interface ContactContent {
bankBranch: string
bankAccountNumber: string
bankIban: string
bankStyleClass: string
workHoursTitle: string
workHoursTitleKey: string
workWeekday: string
@ -51,6 +62,7 @@ interface ContactContent {
workWeekendKey: string
workWhatsapp: string
workWhatsappKey: string
workHoursStyleClass: string
mapTitle: string
mapTitleKey: string
mapSrc: string
@ -59,6 +71,8 @@ interface ContactContent {
mapAllowFullScreen: string
mapLoading: string
mapReferrerPolicy: string
mapContainerStyleClass: string
mapFrameStyleClass: string
}
const CONTACT_HERO_TITLE_KEY = 'App.Contact'
@ -67,13 +81,13 @@ const CONTACT_HERO_IMAGE_KEY = 'Public.contact.heroImage'
const CONTACT_HERO_IMAGE_DEFAULT =
'https://images.pexels.com/photos/3183171/pexels-photo-3183171.jpeg?auto=compress&cs=tinysrgb&w=1920'
const CONTACT_INFO_TITLE_KEY = 'Abp.Identity.User.UserInformation.ContactInformation'
const CONTACT_ADDRESS_KEY = 'Public.contact.address.full'
const CONTACT_BANK_TITLE_KEY = 'Public.contact.bank.title'
const CONTACT_WORK_HOURS_TITLE_KEY = 'App.Definitions.WorkHour'
const CONTACT_WORK_WEEKDAY_KEY = 'Public.contact.workHours.weekday'
const CONTACT_WORK_WEEKEND_KEY = 'Public.contact.workHours.weekend'
const CONTACT_WORK_WHATSAPP_KEY = 'Public.contact.workHours.whatsapp'
const CONTACT_MAP_TITLE_KEY = 'Public.contact.location'
const CONTACT_HERO_SECTION_STYLE_KEY = 'Public.contact.hero.sectionStyleClass'
const CONTACT_HERO_TITLE_STYLE_KEY = 'Public.contact.hero.titleStyleClass'
const CONTACT_HERO_SUBTITLE_STYLE_KEY = 'Public.contact.hero.subtitleStyleClass'
const CONTACT_INFO_CARD_STYLE_KEY = 'Public.contact.info.cardStyleClass'
const CONTACT_INFO_TITLE_STYLE_KEY = 'Public.contact.info.titleStyleClass'
function isLikelyLocalizationKey(value?: string) {
return Boolean(value && /^[A-Za-z0-9_.-]+$/.test(value) && value.includes('.'))
@ -103,14 +117,44 @@ function buildContactContent(
return {
heroTitle: resolveLocalizedValue(translate, CONTACT_HERO_TITLE_KEY, 'Contact'),
heroTitleKey: CONTACT_HERO_TITLE_KEY,
heroSectionStyleClass: resolveLocalizedValue(
translate,
CONTACT_HERO_SECTION_STYLE_KEY,
'relative bg-blue-900 py-12 text-white',
),
heroSectionStyleClassKey: CONTACT_HERO_SECTION_STYLE_KEY,
heroTitleStyleClass: resolveLocalizedValue(
translate,
CONTACT_HERO_TITLE_STYLE_KEY,
'ml-4 mb-2 mt-3 text-5xl font-bold text-white',
),
heroTitleStyleClassKey: CONTACT_HERO_TITLE_STYLE_KEY,
heroSubtitle: resolveLocalizedValue(translate, CONTACT_HERO_SUBTITLE_KEY),
heroSubtitleKey: CONTACT_HERO_SUBTITLE_KEY,
heroSubtitleStyleClass: resolveLocalizedValue(
translate,
CONTACT_HERO_SUBTITLE_STYLE_KEY,
'ml-4 max-w-3xl text-xl',
),
heroSubtitleStyleClassKey: CONTACT_HERO_SUBTITLE_STYLE_KEY,
heroImage: resolveLocalizedValue(translate, CONTACT_HERO_IMAGE_KEY, CONTACT_HERO_IMAGE_DEFAULT),
heroImageKey: CONTACT_HERO_IMAGE_KEY,
contactInfoTitle: resolveLocalizedValue(translate, CONTACT_INFO_TITLE_KEY),
contactInfoTitleKey: CONTACT_INFO_TITLE_KEY,
address: resolveLocalizedValue(translate, contact?.address || CONTACT_ADDRESS_KEY),
addressKey: contact?.address || CONTACT_ADDRESS_KEY,
contactInfoCardStyleClass: resolveLocalizedValue(
translate,
CONTACT_INFO_CARD_STYLE_KEY,
'rounded-xl bg-white p-8 shadow-lg',
),
contactInfoCardStyleClassKey: CONTACT_INFO_CARD_STYLE_KEY,
contactInfoTitleStyleClass: resolveLocalizedValue(
translate,
CONTACT_INFO_TITLE_STYLE_KEY,
'mb-6 text-2xl font-bold text-gray-900',
),
contactInfoTitleStyleClassKey: CONTACT_INFO_TITLE_STYLE_KEY,
address: resolveLocalizedValue(translate, contact?.address, ''),
addressKey: contact?.address || '',
phoneNumber: contact?.phoneNumber || '',
email: contact?.email || '',
location: contact?.location || '',
@ -121,25 +165,27 @@ function buildContactContent(
bankBranch: contact?.bankDto?.branch || '',
bankAccountNumber: contact?.bankDto?.accountNumber || '',
bankIban: contact?.bankDto?.iban || '',
bankStyleClass: contact?.bankDto?.styleClass || 'rounded-xl bg-white p-8 shadow-lg',
workHoursTitle: resolveLocalizedValue(translate, CONTACT_WORK_HOURS_TITLE_KEY),
workHoursTitleKey: CONTACT_WORK_HOURS_TITLE_KEY,
workWeekday: resolveLocalizedValue(translate, contact?.workHoursDto?.weekday || CONTACT_WORK_WEEKDAY_KEY),
workWeekdayKey: contact?.workHoursDto?.weekday || CONTACT_WORK_WEEKDAY_KEY,
workWeekend: resolveLocalizedValue(translate, contact?.workHoursDto?.weekend || CONTACT_WORK_WEEKEND_KEY),
workWeekendKey: contact?.workHoursDto?.weekend || CONTACT_WORK_WEEKEND_KEY,
workWhatsapp: resolveLocalizedValue(
translate,
contact?.workHoursDto?.whatsapp || CONTACT_WORK_WHATSAPP_KEY,
),
workWhatsappKey: contact?.workHoursDto?.whatsapp || CONTACT_WORK_WHATSAPP_KEY,
mapTitle: resolveLocalizedValue(translate, contact?.mapDto?.title || CONTACT_MAP_TITLE_KEY),
mapTitleKey: contact?.mapDto?.title || CONTACT_MAP_TITLE_KEY,
workWeekday: resolveLocalizedValue(translate, contact?.workHoursDto?.weekday, ''),
workWeekdayKey: contact?.workHoursDto?.weekday || '',
workWeekend: resolveLocalizedValue(translate, contact?.workHoursDto?.weekend, ''),
workWeekendKey: contact?.workHoursDto?.weekend || '',
workWhatsapp: resolveLocalizedValue(translate, contact?.workHoursDto?.whatsapp, ''),
workWhatsappKey: contact?.workHoursDto?.whatsapp || '',
workHoursStyleClass: contact?.workHoursDto?.styleClass || 'rounded-xl bg-white p-8 shadow-lg',
mapTitle: resolveLocalizedValue(translate, contact?.mapDto?.title, ''),
mapTitleKey: contact?.mapDto?.title || '',
mapSrc: contact?.mapDto?.src || '',
mapWidth: contact?.mapDto?.width || '100%',
mapHeight: contact?.mapDto?.height || '700',
mapAllowFullScreen: String(contact?.mapDto?.allowFullScreen ?? true),
mapLoading: contact?.mapDto?.loading || 'lazy',
mapReferrerPolicy: contact?.mapDto?.referrerPolicy || 'no-referrer-when-downgrade',
mapContainerStyleClass: contact?.mapDto?.containerStyleClass || 'rounded-xl bg-white p-8 shadow-lg',
mapFrameStyleClass:
contact?.mapDto?.frameStyleClass || 'aspect-w-16 aspect-h-9 overflow-hidden rounded-xl bg-gray-200',
}
}
@ -238,18 +284,36 @@ const Contact: React.FC = () => {
type: 'text',
value: content.heroTitle,
},
{
key: 'heroTitleStyleClass',
label: content.heroTitleStyleClassKey,
type: 'text',
value: content.heroTitleStyleClass,
},
{
key: 'heroSubtitle',
label: content.heroSubtitleKey,
type: 'textarea',
value: content.heroSubtitle,
},
{
key: 'heroSubtitleStyleClass',
label: content.heroSubtitleStyleClassKey,
type: 'text',
value: content.heroSubtitleStyleClass,
},
{
key: 'heroImage',
label: content.heroImageKey,
type: 'image',
value: content.heroImage,
},
{
key: 'heroSectionStyleClass',
label: content.heroSectionStyleClassKey,
type: 'text',
value: content.heroSectionStyleClass,
},
],
}
}
@ -266,6 +330,18 @@ const Contact: React.FC = () => {
type: 'text',
value: content.contactInfoTitle,
},
{
key: 'contactInfoTitleStyleClass',
label: content.contactInfoTitleStyleClassKey,
type: 'text',
value: content.contactInfoTitleStyleClass,
},
{
key: 'contactInfoCardStyleClass',
label: content.contactInfoCardStyleClassKey,
type: 'text',
value: content.contactInfoCardStyleClass,
},
{
key: 'address',
label: content.addressKey,
@ -336,6 +412,12 @@ const Contact: React.FC = () => {
type: 'text',
value: content.bankIban,
},
{
key: 'bankStyleClass',
label: 'Public.contact.bank.styleClass',
type: 'text',
value: content.bankStyleClass,
},
],
}
}
@ -370,6 +452,12 @@ const Contact: React.FC = () => {
type: 'text',
value: content.workWhatsapp,
},
{
key: 'workHoursStyleClass',
label: 'Public.contact.workHours.styleClass',
type: 'text',
value: content.workHoursStyleClass,
},
],
}
}
@ -423,6 +511,18 @@ const Contact: React.FC = () => {
type: 'text',
value: content.mapReferrerPolicy,
},
{
key: 'mapContainerStyleClass',
label: 'Public.contact.map.containerStyleClass',
type: 'text',
value: content.mapContainerStyleClass,
},
{
key: 'mapFrameStyleClass',
label: 'Public.contact.map.frameStyleClass',
type: 'text',
value: content.mapFrameStyleClass,
},
],
}
}
@ -460,6 +560,7 @@ const Contact: React.FC = () => {
bankBranch: content.bankBranch,
bankAccountNumber: content.bankAccountNumber,
bankIban: content.bankIban,
bankStyleClass: content.bankStyleClass,
workHoursTitleKey: content.workHoursTitleKey,
workHoursTitleValue: content.workHoursTitle,
workWeekdayKey: content.workWeekdayKey,
@ -468,6 +569,7 @@ const Contact: React.FC = () => {
workWeekendValue: content.workWeekend,
workWhatsappKey: content.workWhatsappKey,
workWhatsappValue: content.workWhatsapp,
workHoursStyleClass: content.workHoursStyleClass,
mapTitleKey: content.mapTitleKey,
mapTitleValue: content.mapTitle,
mapSrc: content.mapSrc,
@ -476,6 +578,30 @@ const Contact: React.FC = () => {
mapAllowFullScreen: content.mapAllowFullScreen.toLowerCase() === 'true',
mapLoading: content.mapLoading,
mapReferrerPolicy: content.mapReferrerPolicy,
mapContainerStyleClass: content.mapContainerStyleClass,
mapFrameStyleClass: content.mapFrameStyleClass,
styleTexts: [
{
key: content.heroSectionStyleClassKey,
value: content.heroSectionStyleClass,
},
{
key: content.heroTitleStyleClassKey,
value: content.heroTitleStyleClass,
},
{
key: content.heroSubtitleStyleClassKey,
value: content.heroSubtitleStyleClass,
},
{
key: content.contactInfoCardStyleClassKey,
value: content.contactInfoCardStyleClass,
},
{
key: content.contactInfoTitleStyleClassKey,
value: content.contactInfoTitleStyleClass,
},
],
})
await getConfig(false)
@ -548,7 +674,7 @@ const Contact: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="relative bg-blue-900 py-12 text-white">
<div className={content?.heroSectionStyleClass || 'relative bg-blue-900 py-12 text-white'}>
<div
className="absolute inset-0 opacity-20"
style={{
@ -558,8 +684,8 @@ const Contact: React.FC = () => {
}}
></div>
<div className="container relative mx-auto pt-20">
<h1 className="ml-4 mb-2 mt-3 text-5xl font-bold text-white">{content?.heroTitle}</h1>
<p className="ml-4 max-w-3xl text-xl">{content?.heroSubtitle}</p>
<h1 className={content?.heroTitleStyleClass || 'ml-4 mb-2 mt-3 text-5xl font-bold text-white'}>{content?.heroTitle}</h1>
<p className={content?.heroSubtitleStyleClass || 'ml-4 max-w-3xl text-xl'}>{content?.heroSubtitle}</p>
</div>
</div>
</SelectableBlock>
@ -574,8 +700,8 @@ const Contact: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="rounded-xl bg-white p-8 shadow-lg">
<h2 className="mb-6 text-2xl font-bold text-gray-900">{content?.contactInfoTitle}</h2>
<div className={content?.contactInfoCardStyleClass || 'rounded-xl bg-white p-8 shadow-lg'}>
<h2 className={content?.contactInfoTitleStyleClass || 'mb-6 text-2xl font-bold text-gray-900'}>{content?.contactInfoTitle}</h2>
<div className="space-y-4">
<div className="flex items-start space-x-2">
<FaMapMarkerAlt className="mt-1 h-5 w-5 flex-shrink-0 text-blue-600" />
@ -621,7 +747,7 @@ const Contact: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="rounded-xl bg-white p-8 shadow-lg">
<div className={content?.bankStyleClass || 'rounded-xl bg-white p-8 shadow-lg'}>
<h2 className="mb-6 text-2xl font-bold text-gray-900">{content?.bankTitle}</h2>
<div className="mb-2">
<img
@ -645,7 +771,7 @@ const Contact: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="rounded-xl bg-white p-8 shadow-lg">
<div className={content?.workHoursStyleClass || 'rounded-xl bg-white p-8 shadow-lg'}>
<h2 className="mb-6 text-2xl font-bold text-gray-900">{content?.workHoursTitle}</h2>
<div className="space-y-2">
<div className="flex items-center space-x-2">
@ -671,9 +797,9 @@ const Contact: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="rounded-xl bg-white p-8 shadow-lg">
<div className={content?.mapContainerStyleClass || 'rounded-xl bg-white p-8 shadow-lg'}>
<h2 className="mb-2 text-center text-2xl font-bold text-gray-900">{content?.mapTitle}</h2>
<div className="aspect-w-16 aspect-h-9 overflow-hidden rounded-xl bg-gray-200">
<div className={content?.mapFrameStyleClass || 'aspect-w-16 aspect-h-9 overflow-hidden rounded-xl bg-gray-200'}>
<iframe
src={content?.mapSrc}
width={content?.mapWidth}

View file

@ -25,6 +25,7 @@ interface HomeSlideServiceContent {
titleKey: string
description: string
descriptionKey: string
styleClass: string
}
interface HomeSlideContent {
@ -32,6 +33,7 @@ interface HomeSlideContent {
titleKey: string
subtitle: string
subtitleKey: string
styleClass: string
services: HomeSlideServiceContent[]
}
@ -41,6 +43,7 @@ interface HomeFeatureContent {
titleKey: string
description: string
descriptionKey: string
styleClass: string
}
interface HomeSolutionContent {
@ -50,6 +53,7 @@ interface HomeSolutionContent {
titleKey: string
description: string
descriptionKey: string
styleClass: string
}
interface HomeContent {
@ -57,40 +61,75 @@ interface HomeContent {
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: HomeFeatureContent[]
solutions: HomeSolutionContent[]
}
const HOME_HERO_BACKGROUND_KEY = 'Public.home.hero.backgroundImage'
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 HOME_HERO_PRIMARY_CTA_KEY = 'Public.hero.cta.consultation'
const HOME_HERO_SECONDARY_CTA_KEY = 'Public.hero.cta.discover'
const HOME_FEATURES_TITLE_KEY = 'Public.features.title'
const HOME_FEATURES_SUBTITLE_KEY = 'Public.features.subtitle'
const HOME_SOLUTIONS_TITLE_KEY = 'Public.solutions.title'
const HOME_SOLUTIONS_SUBTITLE_KEY = 'Public.solutions.subtitle'
const HOME_CTA_TITLE_KEY = 'Public.common.getStarted'
const HOME_CTA_SUBTITLE_KEY = 'Public.common.contact'
const HOME_CTA_BUTTON_KEY = 'Public.common.learnMore'
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'
const HOME_FEATURES_TITLE_STYLE_DEFAULT = 'text-4xl font-bold text-gray-900 mb-4'
const HOME_FEATURES_SUBTITLE_STYLE_DEFAULT = 'text-xl text-gray-600 max-w-2xl mx-auto'
const HOME_SOLUTIONS_TITLE_STYLE_DEFAULT = 'text-4xl font-bold text-gray-900 mb-4'
const HOME_SOLUTIONS_SUBTITLE_STYLE_DEFAULT = 'text-xl text-gray-600 max-w-2xl mx-auto'
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 =
'p-8 bg-white rounded-xl shadow-lg hover:shadow-xl transition-shadow'
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('.'))
@ -113,206 +152,121 @@ function resolveLocalizedValue(
return translatedValue === keyOrValue ? fallback || keyOrValue : translatedValue
}
function defaultSlides() {
return [
{
titleKey: 'Public.hero.slide1.title',
subtitleKey: 'Public.hero.slide1.subtitle',
services: [
{
icon: 'FaCalendarAlt',
titleKey: 'Public.hero.slide1.service1.title',
descriptionKey: 'Public.hero.slide1.service1.desc',
},
{
icon: 'FaUsers',
titleKey: 'Public.hero.slide1.service2.title',
descriptionKey: 'Public.hero.slide1.service2.desc',
},
{
icon: 'FaShieldAlt',
titleKey: 'Public.hero.slide1.service3.title',
descriptionKey: 'Public.hero.slide1.service3.desc',
},
],
},
{
titleKey: 'Public.hero.slide2.title',
subtitleKey: 'Public.hero.slide2.subtitle',
services: [
{
icon: 'FaChartBar',
titleKey: 'Public.hero.slide2.service1.title',
descriptionKey: 'Public.hero.slide2.service1.desc',
},
{
icon: 'FaCreditCard',
titleKey: 'Public.hero.slide2.service2.title',
descriptionKey: 'Public.hero.slide2.service2.desc',
},
{
icon: 'FaDatabase',
titleKey: 'Public.hero.slide2.service3.title',
descriptionKey: 'Public.hero.slide2.service3.desc',
},
],
},
{
titleKey: 'Public.hero.slide3.title',
subtitleKey: 'Public.hero.slide3.subtitle',
services: [
{
icon: 'FaDesktop',
titleKey: 'Public.hero.slide3.service1.title',
descriptionKey: 'Public.hero.slide3.service1.desc',
},
{
icon: 'FaServer',
titleKey: 'Public.hero.slide3.service2.title',
descriptionKey: 'Public.hero.slide3.service2.desc',
},
{
icon: 'FaMobileAlt',
titleKey: 'Public.hero.slide3.service3.title',
descriptionKey: 'Public.hero.slide3.service3.desc',
},
],
},
]
}
function defaultFeatures() {
return [
{ icon: 'FaUsers', titleKey: 'Public.features.reliable', descriptionKey: 'Public.features.reliable.desc' },
{
icon: 'FaCalendarAlt',
titleKey: 'App.Coordinator.Classroom.Planning',
descriptionKey: 'Public.features.rapid.desc',
},
{ icon: 'FaBookOpen', titleKey: 'Public.features.expert', descriptionKey: 'Public.features.expert.desc' },
{
icon: 'FaCreditCard',
titleKey: 'Public.features.muhasebe',
descriptionKey: 'Public.features.muhasebe.desc',
},
{
icon: 'FaRegComment',
titleKey: 'Public.features.iletisim',
descriptionKey: 'Public.features.iletisim.desc',
},
{ icon: 'FaPhone', titleKey: 'Public.features.mobil', descriptionKey: 'Public.features.mobil.desc' },
{ icon: 'FaChartBar', titleKey: 'Public.features.scalable', descriptionKey: 'Public.features.scalable.desc' },
{
icon: 'FaShieldAlt',
titleKey: 'Public.features.guvenlik',
descriptionKey: 'Public.features.guvenlik.desc',
},
]
}
function defaultSolutions() {
return [
{
icon: 'FaDesktop',
colorClass: 'bg-blue-600',
titleKey: 'Public.services.web.title',
descriptionKey: 'Public.solutions.web.desc',
},
{
icon: 'FaMobileAlt',
colorClass: 'bg-purple-600',
titleKey: 'Public.services.mobile.title',
descriptionKey: 'Public.solutions.mobile.desc',
},
{
icon: 'FaServer',
colorClass: 'bg-green-600',
titleKey: 'Public.solutions.custom.title',
descriptionKey: 'Public.solutions.custom.desc',
},
{
icon: 'FaDatabase',
colorClass: 'bg-red-600',
titleKey: 'Public.solutions.database.title',
descriptionKey: 'Public.solutions.database.desc',
},
]
}
function buildHomeContent(home: HomeDto | undefined, translate: (key: string) => string): HomeContent {
const slideItems = home?.slidesDto?.length ? home.slidesDto : defaultSlides()
const featureItems = home?.featuresDto?.length ? home.featuresDto : defaultFeatures()
const solutionItems = home?.solutionsDto?.length ? home.solutionsDto : defaultSolutions()
const slideItems = home?.slidesDto || []
const featureItems = home?.featuresDto || []
const solutionItems = home?.solutionsDto || []
return {
heroBackgroundImage: resolveLocalizedValue(
translate,
home?.heroBackgroundImageKey || HOME_HERO_BACKGROUND_KEY,
home?.heroBackgroundImageKey,
HOME_HERO_DEFAULT_IMAGE,
),
heroBackgroundImageKey: home?.heroBackgroundImageKey || HOME_HERO_BACKGROUND_KEY,
heroPrimaryCtaLabel: resolveLocalizedValue(
heroBackgroundImageKey: home?.heroBackgroundImageKey || '',
heroPrimaryCtaLabel: resolveLocalizedValue(translate, home?.heroPrimaryCtaKey, ''),
heroPrimaryCtaKey: home?.heroPrimaryCtaKey || '',
heroPrimaryCtaStyle: resolveLocalizedValue(
translate,
home?.heroPrimaryCtaKey || HOME_HERO_PRIMARY_CTA_KEY,
HOME_HERO_PRIMARY_CTA_STYLE_KEY,
HOME_HERO_PRIMARY_CTA_STYLE_DEFAULT,
),
heroPrimaryCtaKey: home?.heroPrimaryCtaKey || HOME_HERO_PRIMARY_CTA_KEY,
heroSecondaryCtaLabel: resolveLocalizedValue(
heroPrimaryCtaStyleKey: HOME_HERO_PRIMARY_CTA_STYLE_KEY,
heroSecondaryCtaLabel: resolveLocalizedValue(translate, home?.heroSecondaryCtaKey, ''),
heroSecondaryCtaKey: home?.heroSecondaryCtaKey || '',
heroSecondaryCtaStyle: resolveLocalizedValue(
translate,
home?.heroSecondaryCtaKey || HOME_HERO_SECONDARY_CTA_KEY,
HOME_HERO_SECONDARY_CTA_STYLE_KEY,
HOME_HERO_SECONDARY_CTA_STYLE_DEFAULT,
),
heroSecondaryCtaKey: home?.heroSecondaryCtaKey || HOME_HERO_SECONDARY_CTA_KEY,
featuresTitle: resolveLocalizedValue(translate, home?.featuresTitleKey || HOME_FEATURES_TITLE_KEY),
featuresTitleKey: home?.featuresTitleKey || HOME_FEATURES_TITLE_KEY,
featuresSubtitle: resolveLocalizedValue(
heroSecondaryCtaStyleKey: HOME_HERO_SECONDARY_CTA_STYLE_KEY,
featuresTitle: resolveLocalizedValue(translate, home?.featuresTitleKey, ''),
featuresTitleKey: home?.featuresTitleKey || '',
featuresTitleStyle: resolveLocalizedValue(
translate,
home?.featuresSubtitleKey || HOME_FEATURES_SUBTITLE_KEY,
HOME_FEATURES_TITLE_STYLE_KEY,
HOME_FEATURES_TITLE_STYLE_DEFAULT,
),
featuresSubtitleKey: home?.featuresSubtitleKey || HOME_FEATURES_SUBTITLE_KEY,
solutionsTitle: resolveLocalizedValue(translate, home?.solutionsTitleKey || HOME_SOLUTIONS_TITLE_KEY),
solutionsTitleKey: home?.solutionsTitleKey || HOME_SOLUTIONS_TITLE_KEY,
solutionsSubtitle: resolveLocalizedValue(
featuresTitleStyleKey: HOME_FEATURES_TITLE_STYLE_KEY,
featuresSubtitle: resolveLocalizedValue(translate, home?.featuresSubtitleKey, ''),
featuresSubtitleKey: home?.featuresSubtitleKey || '',
featuresSubtitleStyle: resolveLocalizedValue(
translate,
home?.solutionsSubtitleKey || HOME_SOLUTIONS_SUBTITLE_KEY,
HOME_FEATURES_SUBTITLE_STYLE_KEY,
HOME_FEATURES_SUBTITLE_STYLE_DEFAULT,
),
solutionsSubtitleKey: home?.solutionsSubtitleKey || HOME_SOLUTIONS_SUBTITLE_KEY,
ctaTitle: resolveLocalizedValue(translate, home?.ctaTitleKey || HOME_CTA_TITLE_KEY),
ctaTitleKey: home?.ctaTitleKey || HOME_CTA_TITLE_KEY,
ctaSubtitle: resolveLocalizedValue(translate, home?.ctaSubtitleKey || HOME_CTA_SUBTITLE_KEY),
ctaSubtitleKey: home?.ctaSubtitleKey || HOME_CTA_SUBTITLE_KEY,
ctaButtonLabel: resolveLocalizedValue(translate, home?.ctaButtonLabelKey || HOME_CTA_BUTTON_KEY),
ctaButtonLabelKey: home?.ctaButtonLabelKey || HOME_CTA_BUTTON_KEY,
slides: slideItems.map((slide, slideIndex) => ({
featuresSubtitleStyleKey: HOME_FEATURES_SUBTITLE_STYLE_KEY,
solutionsTitle: resolveLocalizedValue(translate, home?.solutionsTitleKey, ''),
solutionsTitleKey: home?.solutionsTitleKey || '',
solutionsTitleStyle: resolveLocalizedValue(
translate,
HOME_SOLUTIONS_TITLE_STYLE_KEY,
HOME_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,
HOME_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,
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),
titleKey: slide.titleKey || `Public.home.dynamic.slide.${slideIndex + 1}.title`,
titleKey: slide.titleKey || '',
subtitle: resolveLocalizedValue(translate, slide.subtitleKey, slide.subtitleKey),
subtitleKey: slide.subtitleKey || `Public.home.dynamic.slide.${slideIndex + 1}.subtitle`,
services: (slide.services || []).map((service, serviceIndex) => ({
icon: service.icon || 'FaCircle',
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),
titleKey:
service.titleKey ||
`Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.title`,
titleKey: service.titleKey || '',
description: resolveLocalizedValue(translate, service.descriptionKey, service.descriptionKey),
descriptionKey:
service.descriptionKey ||
`Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.description`,
descriptionKey: service.descriptionKey || '',
styleClass: service.styleClass || HOME_SLIDE_SERVICE_STYLE_DEFAULT,
})),
})),
features: featureItems.map((feature, index) => ({
icon: feature.icon || 'FaCircle',
features: featureItems.map((feature) => ({
icon: feature.icon || '',
title: resolveLocalizedValue(translate, feature.titleKey, feature.titleKey),
titleKey: feature.titleKey || `Public.home.dynamic.feature.${index + 1}.title`,
titleKey: feature.titleKey || '',
description: resolveLocalizedValue(translate, feature.descriptionKey, feature.descriptionKey),
descriptionKey: feature.descriptionKey || `Public.home.dynamic.feature.${index + 1}.description`,
descriptionKey: feature.descriptionKey || '',
styleClass: feature.styleClass || HOME_FEATURE_CARD_STYLE_DEFAULT,
})),
solutions: solutionItems.map((solution, index) => ({
icon: solution.icon || 'FaCircle',
colorClass: solution.colorClass || 'bg-blue-600',
solutions: solutionItems.map((solution) => ({
icon: solution.icon || '',
colorClass: solution.colorClass || '',
title: resolveLocalizedValue(translate, solution.titleKey, solution.titleKey),
titleKey: solution.titleKey || `Public.home.dynamic.solution.${index + 1}.title`,
titleKey: solution.titleKey || '',
description: resolveLocalizedValue(translate, solution.descriptionKey, solution.descriptionKey),
descriptionKey:
solution.descriptionKey || `Public.home.dynamic.solution.${index + 1}.description`,
descriptionKey: solution.descriptionKey || '',
styleClass: solution.styleClass || HOME_SOLUTION_CARD_STYLE_DEFAULT,
})),
}
}
@ -428,14 +382,23 @@ const Home: React.FC = () => {
if (
fieldKey === 'heroBackgroundImage' ||
fieldKey === 'heroPrimaryCtaLabel' ||
fieldKey === 'heroPrimaryCtaStyle' ||
fieldKey === 'heroSecondaryCtaLabel' ||
fieldKey === 'heroSecondaryCtaStyle' ||
fieldKey === 'featuresTitle' ||
fieldKey === 'featuresTitleStyle' ||
fieldKey === 'featuresSubtitle' ||
fieldKey === 'featuresSubtitleStyle' ||
fieldKey === 'solutionsTitle' ||
fieldKey === 'solutionsTitleStyle' ||
fieldKey === 'solutionsSubtitle' ||
fieldKey === 'solutionsSubtitleStyle' ||
fieldKey === 'ctaTitle' ||
fieldKey === 'ctaTitleStyle' ||
fieldKey === 'ctaSubtitle' ||
fieldKey === 'ctaButtonLabel'
fieldKey === 'ctaSubtitleStyle' ||
fieldKey === 'ctaButtonLabel' ||
fieldKey === 'ctaButtonStyle'
) {
return {
...current,
@ -452,12 +415,12 @@ const Home: React.FC = () => {
const slides = [...current.slides]
const target = { ...slides[index] }
if (fieldKey === 'title' || fieldKey === 'subtitle') {
if (fieldKey === 'title' || fieldKey === 'subtitle' || fieldKey === 'styleClass') {
target[fieldKey] = nextValue
} else if (fieldKey.startsWith('service-')) {
const parts = fieldKey.split('-')
const serviceIndex = Number(parts[1])
const serviceField = parts[2] as 'icon' | 'title' | 'description'
const serviceField = parts[2] as 'icon' | 'title' | 'description' | 'styleClass'
const services = [...target.services]
const service = { ...services[serviceIndex] }
service[serviceField] = nextValue
@ -480,7 +443,7 @@ const Home: React.FC = () => {
const features = [...current.features]
const feature = { ...features[index] }
const key = fieldKey as 'icon' | 'title' | 'description'
const key = fieldKey as 'icon' | 'title' | 'description' | 'styleClass'
feature[key] = nextValue
features[index] = feature
@ -498,7 +461,7 @@ const Home: React.FC = () => {
const solutions = [...current.solutions]
const solution = { ...solutions[index] }
const key = fieldKey as 'icon' | 'title' | 'description' | 'colorClass'
const key = fieldKey as 'icon' | 'title' | 'description' | 'colorClass' | 'styleClass'
solution[key] = nextValue
solutions[index] = solution
@ -535,12 +498,24 @@ const Home: React.FC = () => {
type: 'text',
value: content.heroPrimaryCtaLabel,
},
{
key: 'heroPrimaryCtaStyle',
label: content.heroPrimaryCtaStyleKey,
type: 'text',
value: content.heroPrimaryCtaStyle,
},
{
key: 'heroSecondaryCtaLabel',
label: content.heroSecondaryCtaKey,
type: 'text',
value: content.heroSecondaryCtaLabel,
},
{
key: 'heroSecondaryCtaStyle',
label: content.heroSecondaryCtaStyleKey,
type: 'text',
value: content.heroSecondaryCtaStyle,
},
],
}
}
@ -570,6 +545,12 @@ const Home: React.FC = () => {
type: 'textarea',
value: slide.subtitle,
},
{
key: 'styleClass',
label: `Slide${index + 1}.StyleClass`,
type: 'text',
value: slide.styleClass,
},
...slide.services.flatMap((service, serviceIndex) => [
{
key: `service-${serviceIndex}-icon`,
@ -590,6 +571,12 @@ const Home: React.FC = () => {
type: 'textarea' as const,
value: service.description,
},
{
key: `service-${serviceIndex}-styleClass`,
label: `Slide${index + 1}.Service${serviceIndex + 1}.StyleClass`,
type: 'text' as const,
value: service.styleClass,
},
]),
],
}
@ -607,12 +594,24 @@ const Home: React.FC = () => {
type: 'text',
value: content.featuresTitle,
},
{
key: 'featuresTitleStyle',
label: content.featuresTitleStyleKey,
type: 'text',
value: content.featuresTitleStyle,
},
{
key: 'featuresSubtitle',
label: content.featuresSubtitleKey,
type: 'textarea',
value: content.featuresSubtitle,
},
{
key: 'featuresSubtitleStyle',
label: content.featuresSubtitleStyleKey,
type: 'text',
value: content.featuresSubtitleStyle,
},
],
}
}
@ -649,6 +648,12 @@ const Home: React.FC = () => {
type: 'textarea',
value: item.description,
},
{
key: 'styleClass',
label: `Feature${index + 1}.StyleClass`,
type: 'text',
value: item.styleClass,
},
],
}
}
@ -665,12 +670,24 @@ const Home: React.FC = () => {
type: 'text',
value: content.solutionsTitle,
},
{
key: 'solutionsTitleStyle',
label: content.solutionsTitleStyleKey,
type: 'text',
value: content.solutionsTitleStyle,
},
{
key: 'solutionsSubtitle',
label: content.solutionsSubtitleKey,
type: 'textarea',
value: content.solutionsSubtitle,
},
{
key: 'solutionsSubtitleStyle',
label: content.solutionsSubtitleStyleKey,
type: 'text',
value: content.solutionsSubtitleStyle,
},
],
}
}
@ -713,6 +730,12 @@ const Home: React.FC = () => {
type: 'textarea',
value: item.description,
},
{
key: 'styleClass',
label: `Solution${index + 1}.StyleClass`,
type: 'text',
value: item.styleClass,
},
],
}
}
@ -729,18 +752,36 @@ const Home: React.FC = () => {
type: 'text',
value: content.ctaTitle,
},
{
key: 'ctaTitleStyle',
label: content.ctaTitleStyleKey,
type: 'text',
value: content.ctaTitleStyle,
},
{
key: 'ctaSubtitle',
label: content.ctaSubtitleKey,
type: 'textarea',
value: content.ctaSubtitle,
},
{
key: 'ctaSubtitleStyle',
label: content.ctaSubtitleStyleKey,
type: 'text',
value: content.ctaSubtitleStyle,
},
{
key: 'ctaButtonLabel',
label: content.ctaButtonLabelKey,
type: 'text',
value: content.ctaButtonLabel,
},
{
key: 'ctaButtonStyle',
label: content.ctaButtonStyleKey,
type: 'text',
value: content.ctaButtonStyle,
},
],
}
}
@ -762,54 +803,71 @@ const Home: React.FC = () => {
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, slideIndex) => ({
titleKey: slide.titleKey || `Public.home.dynamic.slide.${slideIndex + 1}.title`,
titleKey: slide.titleKey || '',
titleValue: slide.title,
subtitleKey: slide.subtitleKey || `Public.home.dynamic.slide.${slideIndex + 1}.subtitle`,
subtitleKey: slide.subtitleKey || '',
subtitleValue: slide.subtitle,
styleClass: slide.styleClass,
services: slide.services.map((service, serviceIndex) => ({
icon: service.icon,
titleKey:
service.titleKey ||
`Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.title`,
titleKey: service.titleKey || '',
titleValue: service.title,
descriptionKey:
service.descriptionKey ||
`Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.description`,
descriptionKey: service.descriptionKey || '',
descriptionValue: service.description,
styleClass: service.styleClass,
})),
})),
features: content.features.map((feature, index) => ({
icon: feature.icon,
titleKey: feature.titleKey || `Public.home.dynamic.feature.${index + 1}.title`,
titleKey: feature.titleKey || '',
titleValue: feature.title,
descriptionKey: feature.descriptionKey || `Public.home.dynamic.feature.${index + 1}.description`,
descriptionKey: feature.descriptionKey || '',
descriptionValue: feature.description,
styleClass: feature.styleClass,
})),
solutions: content.solutions.map((solution, index) => ({
icon: solution.icon,
colorClass: solution.colorClass,
titleKey: solution.titleKey || `Public.home.dynamic.solution.${index + 1}.title`,
titleKey: solution.titleKey || '',
titleValue: solution.title,
descriptionKey:
solution.descriptionKey || `Public.home.dynamic.solution.${index + 1}.description`,
descriptionKey: solution.descriptionKey || '',
descriptionValue: solution.description,
styleClass: solution.styleClass,
})),
})
@ -918,7 +976,7 @@ const Home: React.FC = () => {
}}
className="h-full"
>
<div className="container mx-auto px-4 pt-32">
<div className={`container mx-auto px-4 pt-32 ${slide.styleClass || ''}`}>
<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}
@ -930,14 +988,14 @@ const Home: React.FC = () => {
<div className="flex flex-col md:flex-row justify-center gap-6 mb-16">
<Link
to={ROUTES_ENUM.public.contact}
className="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"
className={content?.heroPrimaryCtaStyle}
>
{content?.heroPrimaryCtaLabel}{' '}
<FaArrowRight className="ml-2" size={20} />
</Link>
<Link
to={ROUTES_ENUM.public.products}
className="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"
className={content?.heroSecondaryCtaStyle}
>
{content?.heroSecondaryCtaLabel}
</Link>
@ -947,7 +1005,10 @@ const Home: React.FC = () => {
{slide.services.map((service, i) => (
<div
key={i}
className="bg-white/5 backdrop-blur-sm rounded-2xl p-8 text-center hover:scale-105 hover:bg-white/10 transition-all"
className={
service.styleClass ||
'bg-white/5 backdrop-blur-sm rounded-2xl p-8 text-center hover:scale-105 hover:bg-white/10 transition-all'
}
>
{(() => {
const IconComponent = navigationIcon[service.icon || '']
@ -1029,10 +1090,10 @@ const Home: React.FC = () => {
onSelect={handleSelectBlock}
>
<div className="text-center mb-16">
<h2 className="text-4xl font-bold text-gray-900 mb-4">
<h2 className={content?.featuresTitleStyle}>
{content?.featuresTitle}
</h2>
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
<p className={content?.featuresSubtitleStyle}>
{content?.featuresSubtitle}
</p>
</div>
@ -1047,7 +1108,12 @@ const Home: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="p-8 bg-white rounded-xl shadow-lg hover:shadow-xl transition-shadow">
<div
className={
feature.styleClass ||
'p-8 bg-white rounded-xl shadow-lg hover:shadow-xl transition-shadow'
}
>
<div className="mb-6">
{(() => {
const IconComponent = navigationIcon[feature.icon || '']
@ -1073,10 +1139,10 @@ const Home: React.FC = () => {
onSelect={handleSelectBlock}
>
<div className="text-center mb-16">
<h2 className="text-4xl font-bold text-gray-900 mb-4">
<h2 className={content?.solutionsTitleStyle}>
{content?.solutionsTitle}
</h2>
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
<p className={content?.solutionsSubtitleStyle}>
{content?.solutionsSubtitle}
</p>
</div>
@ -1091,7 +1157,7 @@ const Home: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className={`${s.colorClass} p-8 h-full rounded-2xl`}>
<div className={`${s.colorClass} ${s.styleClass || 'p-8 h-full rounded-2xl'}`}>
<div className="mb-6">
{(() => {
const IconComponent = navigationIcon[s.icon || '']
@ -1116,11 +1182,11 @@ const Home: React.FC = () => {
>
<section className="bg-blue-600 py-16">
<div className="container mx-auto px-4 text-center">
<h2 className="text-3xl font-bold text-white mb-4">{content?.ctaTitle}</h2>
<p className="text-white text-lg mb-8">{content?.ctaSubtitle}</p>
<h2 className={content?.ctaTitleStyle}>{content?.ctaTitle}</h2>
<p className={content?.ctaSubtitleStyle}>{content?.ctaSubtitle}</p>
<Link
to={ROUTES_ENUM.public.contact}
className="bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-blue-50 transition-colors"
className={content?.ctaButtonStyle}
>
{content?.ctaButtonLabel}
</Link>

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'
import { FaCheckCircle } from 'react-icons/fa'
import { Link, useNavigate } from 'react-router-dom'
import { Link } from 'react-router-dom'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { Helmet } from 'react-helmet'
@ -23,6 +23,14 @@ interface ServiceCardContent {
titleKey: string
description: string
descriptionKey?: string
cardStyleClassKey: string
cardStyleClass: string
titleStyleClassKey: string
titleStyleClass: string
descriptionStyleClassKey: string
descriptionStyleClass: string
featureStyleClassKey: string
featureStyleClass: string
features: Array<{
key: string
value: string
@ -33,6 +41,10 @@ interface SupportCardContent {
icon: string
title: string
titleKey: string
cardStyleClassKey: string
cardStyleClass: string
titleStyleClassKey: string
titleStyleClass: string
features: Array<{
key: string
value: string
@ -42,22 +54,40 @@ interface SupportCardContent {
interface ServicesContent {
heroTitle: string
heroTitleKey: string
heroSectionStyleClassKey: string
heroSectionStyleClass: string
heroTitleStyleClassKey: string
heroTitleStyleClass: string
heroSubtitle: string
heroSubtitleKey: string
heroSubtitleStyleClassKey: string
heroSubtitleStyleClass: string
heroImage: string
heroImageKey: string
serviceItems: ServiceCardContent[]
supportTitle: string
supportTitleKey: string
supportTitleStyleClassKey: string
supportTitleStyleClass: string
supportButtonStyleClassKey: string
supportButtonStyleClass: string
supportItems: SupportCardContent[]
supportButtonLabel: string
supportButtonLabelKey: string
ctaTitle: string
ctaTitleKey: string
ctaSectionStyleClassKey: string
ctaSectionStyleClass: string
ctaTitleStyleClassKey: string
ctaTitleStyleClass: string
ctaDescription: string
ctaDescriptionKey: string
ctaDescriptionStyleClassKey: string
ctaDescriptionStyleClass: string
ctaButtonLabel: string
ctaButtonLabelKey: string
ctaButtonStyleClassKey: string
ctaButtonStyleClass: string
}
const SERVICES_HERO_IMAGE =
@ -70,6 +100,15 @@ const SERVICES_SUPPORT_BUTTON_KEY = 'Public.services.support.contactButton'
const SERVICES_CTA_TITLE_KEY = 'Public.services.cta.title'
const SERVICES_CTA_DESCRIPTION_KEY = 'Public.services.cta.description'
const SERVICES_CTA_BUTTON_KEY = 'Public.services.support.contactButton'
const SERVICES_HERO_SECTION_STYLE_KEY = 'Public.services.hero.sectionStyleClass'
const SERVICES_HERO_TITLE_STYLE_KEY = 'Public.services.hero.titleStyleClass'
const SERVICES_HERO_SUBTITLE_STYLE_KEY = 'Public.services.hero.subtitleStyleClass'
const SERVICES_SUPPORT_TITLE_STYLE_KEY = 'Public.services.support.titleStyleClass'
const SERVICES_SUPPORT_BUTTON_STYLE_KEY = 'Public.services.support.buttonStyleClass'
const SERVICES_CTA_SECTION_STYLE_KEY = 'Public.services.cta.sectionStyleClass'
const SERVICES_CTA_TITLE_STYLE_KEY = 'Public.services.cta.titleStyleClass'
const SERVICES_CTA_DESCRIPTION_STYLE_KEY = 'Public.services.cta.descriptionStyleClass'
const SERVICES_CTA_BUTTON_STYLE_KEY = 'Public.services.cta.buttonStyleClass'
function isLikelyLocalizationKey(value?: string) {
return Boolean(value && /^[A-Za-z0-9_.-]+$/.test(value) && value.includes('.'))
@ -99,8 +138,26 @@ function buildServicesContent(
return {
heroTitle: resolveLocalizedValue(translate, SERVICES_HERO_TITLE_KEY, 'Services'),
heroTitleKey: SERVICES_HERO_TITLE_KEY,
heroSectionStyleClassKey: SERVICES_HERO_SECTION_STYLE_KEY,
heroSectionStyleClass: resolveLocalizedValue(
translate,
SERVICES_HERO_SECTION_STYLE_KEY,
'relative bg-blue-900 text-white py-12',
),
heroTitleStyleClassKey: SERVICES_HERO_TITLE_STYLE_KEY,
heroTitleStyleClass: resolveLocalizedValue(
translate,
SERVICES_HERO_TITLE_STYLE_KEY,
'text-5xl font-bold ml-4 mt-3 mb-2 text-white',
),
heroSubtitle: resolveLocalizedValue(translate, SERVICES_HERO_SUBTITLE_KEY),
heroSubtitleKey: SERVICES_HERO_SUBTITLE_KEY,
heroSubtitleStyleClassKey: SERVICES_HERO_SUBTITLE_STYLE_KEY,
heroSubtitleStyleClass: resolveLocalizedValue(
translate,
SERVICES_HERO_SUBTITLE_STYLE_KEY,
'text-xl max-w-3xl ml-4',
),
heroImage: resolveLocalizedValue(translate, SERVICES_HERO_IMAGE_KEY, SERVICES_HERO_IMAGE),
heroImageKey: SERVICES_HERO_IMAGE_KEY,
serviceItems: services
@ -115,6 +172,30 @@ function buildServicesContent(
descriptionKey:
(item.description && isLikelyLocalizationKey(item.description) ? item.description : undefined) ||
`Public.services.dynamic.service.${index + 1}.description`,
cardStyleClassKey: `Public.services.dynamic.service.${index + 1}.cardStyleClass`,
cardStyleClass: resolveLocalizedValue(
translate,
`Public.services.dynamic.service.${index + 1}.cardStyleClass`,
'bg-white rounded-xl shadow-lg p-8 hover:shadow-xl transition-shadow',
),
titleStyleClassKey: `Public.services.dynamic.service.${index + 1}.titleStyleClass`,
titleStyleClass: resolveLocalizedValue(
translate,
`Public.services.dynamic.service.${index + 1}.titleStyleClass`,
'text-2xl font-bold text-gray-900 mb-4',
),
descriptionStyleClassKey: `Public.services.dynamic.service.${index + 1}.descriptionStyleClass`,
descriptionStyleClass: resolveLocalizedValue(
translate,
`Public.services.dynamic.service.${index + 1}.descriptionStyleClass`,
'text-gray-600 mb-6',
),
featureStyleClassKey: `Public.services.dynamic.service.${index + 1}.featureStyleClass`,
featureStyleClass: resolveLocalizedValue(
translate,
`Public.services.dynamic.service.${index + 1}.featureStyleClass`,
'flex items-center text-gray-700',
),
features: (item.features ?? []).map((feature, featureIndex) => ({
key:
(isLikelyLocalizationKey(feature) ? feature : undefined) ||
@ -124,6 +205,18 @@ function buildServicesContent(
})),
supportTitle: resolveLocalizedValue(translate, SERVICES_SUPPORT_TITLE_KEY),
supportTitleKey: SERVICES_SUPPORT_TITLE_KEY,
supportTitleStyleClassKey: SERVICES_SUPPORT_TITLE_STYLE_KEY,
supportTitleStyleClass: resolveLocalizedValue(
translate,
SERVICES_SUPPORT_TITLE_STYLE_KEY,
'text-3xl font-bold text-center mb-10',
),
supportButtonStyleClassKey: SERVICES_SUPPORT_BUTTON_STYLE_KEY,
supportButtonStyleClass: resolveLocalizedValue(
translate,
SERVICES_SUPPORT_BUTTON_STYLE_KEY,
'block text-center bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors',
),
supportItems: services
.filter((item) => item.type === 'support')
.map((item, index) => ({
@ -132,6 +225,18 @@ function buildServicesContent(
titleKey:
(isLikelyLocalizationKey(item.title) ? item.title : undefined) ||
`Public.services.dynamic.support.${index + 1}.title`,
cardStyleClassKey: `Public.services.dynamic.support.${index + 1}.cardStyleClass`,
cardStyleClass: resolveLocalizedValue(
translate,
`Public.services.dynamic.support.${index + 1}.cardStyleClass`,
'bg-white rounded-xl shadow-lg p-8 border border-gray-200',
),
titleStyleClassKey: `Public.services.dynamic.support.${index + 1}.titleStyleClass`,
titleStyleClass: resolveLocalizedValue(
translate,
`Public.services.dynamic.support.${index + 1}.titleStyleClass`,
'text-xl font-bold mb-4',
),
features: (item.features ?? []).map((feature, featureIndex) => ({
key:
(isLikelyLocalizationKey(feature) ? feature : undefined) ||
@ -143,16 +248,39 @@ function buildServicesContent(
supportButtonLabelKey: SERVICES_SUPPORT_BUTTON_KEY,
ctaTitle: resolveLocalizedValue(translate, SERVICES_CTA_TITLE_KEY),
ctaTitleKey: SERVICES_CTA_TITLE_KEY,
ctaSectionStyleClassKey: SERVICES_CTA_SECTION_STYLE_KEY,
ctaSectionStyleClass: resolveLocalizedValue(
translate,
SERVICES_CTA_SECTION_STYLE_KEY,
'bg-blue-900 text-white py-16',
),
ctaTitleStyleClassKey: SERVICES_CTA_TITLE_STYLE_KEY,
ctaTitleStyleClass: resolveLocalizedValue(
translate,
SERVICES_CTA_TITLE_STYLE_KEY,
'text-3xl font-bold mb-6 text-white',
),
ctaDescription: resolveLocalizedValue(translate, SERVICES_CTA_DESCRIPTION_KEY),
ctaDescriptionKey: SERVICES_CTA_DESCRIPTION_KEY,
ctaDescriptionStyleClassKey: SERVICES_CTA_DESCRIPTION_STYLE_KEY,
ctaDescriptionStyleClass: resolveLocalizedValue(
translate,
SERVICES_CTA_DESCRIPTION_STYLE_KEY,
'text-xl mb-8 max-w-2xl mx-auto',
),
ctaButtonLabel: resolveLocalizedValue(translate, SERVICES_CTA_BUTTON_KEY),
ctaButtonLabelKey: SERVICES_CTA_BUTTON_KEY,
ctaButtonStyleClassKey: SERVICES_CTA_BUTTON_STYLE_KEY,
ctaButtonStyleClass: resolveLocalizedValue(
translate,
SERVICES_CTA_BUTTON_STYLE_KEY,
'bg-white text-blue-900 px-8 py-3 rounded-lg font-semibold hover:bg-blue-50 transition-colors',
),
}
}
const Services: React.FC = () => {
const { translate } = useLocalization()
const navigate = useNavigate()
const { setLang } = useStoreActions((actions) => actions.locale)
const { getConfig } = useStoreActions((actions) => actions.abpConfig)
const configCultureName = useStoreState(
@ -249,11 +377,20 @@ const Services: React.FC = () => {
fieldKey === 'heroTitle' ||
fieldKey === 'heroSubtitle' ||
fieldKey === 'heroImage' ||
fieldKey === 'heroSectionStyleClass' ||
fieldKey === 'heroTitleStyleClass' ||
fieldKey === 'heroSubtitleStyleClass' ||
fieldKey === 'supportTitle' ||
fieldKey === 'supportTitleStyleClass' ||
fieldKey === 'supportButtonLabel' ||
fieldKey === 'supportButtonStyleClass' ||
fieldKey === 'ctaTitle' ||
fieldKey === 'ctaSectionStyleClass' ||
fieldKey === 'ctaTitleStyleClass' ||
fieldKey === 'ctaDescription' ||
fieldKey === 'ctaDescriptionStyleClass' ||
fieldKey === 'ctaButtonLabel'
|| fieldKey === 'ctaButtonStyleClass'
) {
return {
...current,
@ -360,18 +497,36 @@ const Services: React.FC = () => {
type: 'text',
value: content.heroTitle,
},
{
key: 'heroTitleStyleClass',
label: content.heroTitleStyleClassKey,
type: 'text',
value: content.heroTitleStyleClass,
},
{
key: 'heroSubtitle',
label: content.heroSubtitleKey,
type: 'textarea',
value: content.heroSubtitle,
},
{
key: 'heroSubtitleStyleClass',
label: content.heroSubtitleStyleClassKey,
type: 'text',
value: content.heroSubtitleStyleClass,
},
{
key: 'heroImage',
label: content.heroImageKey,
type: 'image',
value: content.heroImage,
},
{
key: 'heroSectionStyleClass',
label: content.heroSectionStyleClassKey,
type: 'text',
value: content.heroSectionStyleClass,
},
],
}
}
@ -388,12 +543,24 @@ const Services: React.FC = () => {
type: 'text',
value: content.supportTitle,
},
{
key: 'supportTitleStyleClass',
label: content.supportTitleStyleClassKey,
type: 'text',
value: content.supportTitleStyleClass,
},
{
key: 'supportButtonLabel',
label: content.supportButtonLabelKey,
type: 'text',
value: content.supportButtonLabel,
},
{
key: 'supportButtonStyleClass',
label: content.supportButtonStyleClassKey,
type: 'text',
value: content.supportButtonStyleClass,
},
],
}
}
@ -410,18 +577,42 @@ const Services: React.FC = () => {
type: 'text',
value: content.ctaTitle,
},
{
key: 'ctaTitleStyleClass',
label: content.ctaTitleStyleClassKey,
type: 'text',
value: content.ctaTitleStyleClass,
},
{
key: 'ctaDescription',
label: content.ctaDescriptionKey,
type: 'textarea',
value: content.ctaDescription,
},
{
key: 'ctaDescriptionStyleClass',
label: content.ctaDescriptionStyleClassKey,
type: 'text',
value: content.ctaDescriptionStyleClass,
},
{
key: 'ctaButtonLabel',
label: content.ctaButtonLabelKey,
type: 'text',
value: content.ctaButtonLabel,
},
{
key: 'ctaButtonStyleClass',
label: content.ctaButtonStyleClassKey,
type: 'text',
value: content.ctaButtonStyleClass,
},
{
key: 'ctaSectionStyleClass',
label: content.ctaSectionStyleClassKey,
type: 'text',
value: content.ctaSectionStyleClass,
},
],
}
}
@ -458,6 +649,30 @@ const Services: React.FC = () => {
type: 'textarea',
value: item.description,
},
{
key: 'cardStyleClass',
label: item.cardStyleClassKey,
type: 'text',
value: item.cardStyleClass,
},
{
key: 'titleStyleClass',
label: item.titleStyleClassKey,
type: 'text',
value: item.titleStyleClass,
},
{
key: 'descriptionStyleClass',
label: item.descriptionStyleClassKey,
type: 'text',
value: item.descriptionStyleClass,
},
{
key: 'featureStyleClass',
label: item.featureStyleClassKey,
type: 'text',
value: item.featureStyleClass,
},
...item.features.map((feature, featureIndex) => ({
key: `feature-${featureIndex}`,
label:
@ -495,6 +710,18 @@ const Services: React.FC = () => {
type: 'text',
value: item.title,
},
{
key: 'cardStyleClass',
label: item.cardStyleClassKey,
type: 'text',
value: item.cardStyleClass,
},
{
key: 'titleStyleClass',
label: item.titleStyleClassKey,
type: 'text',
value: item.titleStyleClass,
},
...item.features.map((feature, featureIndex) => ({
key: `feature-${featureIndex}`,
label:
@ -560,6 +787,72 @@ const Services: React.FC = () => {
value: feature.value,
})),
})),
styleTexts: [
{
key: content.heroSectionStyleClassKey,
value: content.heroSectionStyleClass,
},
{
key: content.heroTitleStyleClassKey,
value: content.heroTitleStyleClass,
},
{
key: content.heroSubtitleStyleClassKey,
value: content.heroSubtitleStyleClass,
},
{
key: content.supportTitleStyleClassKey,
value: content.supportTitleStyleClass,
},
{
key: content.supportButtonStyleClassKey,
value: content.supportButtonStyleClass,
},
{
key: content.ctaSectionStyleClassKey,
value: content.ctaSectionStyleClass,
},
{
key: content.ctaTitleStyleClassKey,
value: content.ctaTitleStyleClass,
},
{
key: content.ctaDescriptionStyleClassKey,
value: content.ctaDescriptionStyleClass,
},
{
key: content.ctaButtonStyleClassKey,
value: content.ctaButtonStyleClass,
},
...content.serviceItems.flatMap((item) => [
{
key: item.cardStyleClassKey,
value: item.cardStyleClass,
},
{
key: item.titleStyleClassKey,
value: item.titleStyleClass,
},
{
key: item.descriptionStyleClassKey,
value: item.descriptionStyleClass,
},
{
key: item.featureStyleClassKey,
value: item.featureStyleClass,
},
]),
...content.supportItems.flatMap((item) => [
{
key: item.cardStyleClassKey,
value: item.cardStyleClass,
},
{
key: item.titleStyleClassKey,
value: item.titleStyleClass,
},
]),
],
})
await getConfig(false)
@ -628,7 +921,7 @@ const Services: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="relative bg-blue-900 text-white py-12">
<div className={content?.heroSectionStyleClass || 'relative bg-blue-900 text-white py-12'}>
<div
className="absolute inset-0 opacity-20"
style={{
@ -638,8 +931,8 @@ const Services: React.FC = () => {
}}
></div>
<div className="container mx-auto pt-20 relative">
<h1 className="text-5xl font-bold ml-4 mt-3 mb-2 text-white">{content?.heroTitle}</h1>
<p className="text-xl max-w-3xl ml-4">{content?.heroSubtitle}</p>
<h1 className={content?.heroTitleStyleClass || 'text-5xl font-bold ml-4 mt-3 mb-2 text-white'}>{content?.heroTitle}</h1>
<p className={content?.heroSubtitleStyleClass || 'text-xl max-w-3xl ml-4'}>{content?.heroSubtitle}</p>
</div>
</div>
</SelectableBlock>
@ -658,17 +951,17 @@ const Services: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="bg-white rounded-xl shadow-lg p-8 hover:shadow-xl transition-shadow">
<div className={service.cardStyleClass || 'bg-white rounded-xl shadow-lg p-8 hover:shadow-xl transition-shadow'}>
<div className="mb-6">
{IconComponent && (
<IconComponent className={`w-12 h-12 ${getIconColor(index)}`} />
)}
</div>
<h3 className="text-2xl font-bold text-gray-900 mb-4">{service.title}</h3>
<p className="text-gray-600 mb-6">{service.description}</p>
<h3 className={service.titleStyleClass || 'text-2xl font-bold text-gray-900 mb-4'}>{service.title}</h3>
<p className={service.descriptionStyleClass || 'text-gray-600 mb-6'}>{service.description}</p>
<ul className="space-y-2">
{(service.features ?? []).map((feature, fIndex) => (
<li key={fIndex} className="flex items-center text-gray-700">
<li key={fIndex} className={service.featureStyleClass || 'flex items-center text-gray-700'}>
<span className="w-2 h-2 bg-blue-600 rounded-full mr-2"></span>
{feature.value}
</li>
@ -691,7 +984,7 @@ const Services: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<h2 className="text-3xl font-bold text-center mb-10">{content?.supportTitle}</h2>
<h2 className={content?.supportTitleStyleClass || 'text-3xl font-bold text-center mb-10'}>{content?.supportTitle}</h2>
</SelectableBlock>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{content?.supportItems.map((plan, index) => {
@ -705,13 +998,13 @@ const Services: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="bg-white rounded-xl shadow-lg p-8 border border-gray-200">
<div className={plan.cardStyleClass || 'bg-white rounded-xl shadow-lg p-8 border border-gray-200'}>
<div className="mb-6">
{IconComponent && (
<IconComponent className={`w-12 h-12 ${getIconColor(index)}`} />
)}
</div>
<h3 className="text-xl font-bold mb-4">{plan.title}</h3>
<h3 className={plan.titleStyleClass || 'text-xl font-bold mb-4'}>{plan.title}</h3>
<ul className="space-y-3 mb-8">
{(plan.features ?? []).map((feature, fIndex) => (
<li key={fIndex} className="flex items-center space-x-2 text-gray-700">
@ -722,7 +1015,7 @@ const Services: React.FC = () => {
</ul>
<Link
to={ROUTES_ENUM.public.contact}
className="block text-center bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors"
className={content?.supportButtonStyleClass || 'block text-center bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors'}
>
{content?.supportButtonLabel}
</Link>
@ -741,13 +1034,13 @@ const Services: React.FC = () => {
isDesignMode={isDesignMode}
onSelect={handleSelectBlock}
>
<div className="bg-blue-900 text-white py-16">
<div className={content?.ctaSectionStyleClass || 'bg-blue-900 text-white py-16'}>
<div className="container mx-auto px-4 text-center">
<h2 className="text-3xl font-bold mb-6 text-white">{content?.ctaTitle}</h2>
<p className="text-xl mb-8 max-w-2xl mx-auto">{content?.ctaDescription}</p>
<h2 className={content?.ctaTitleStyleClass || 'text-3xl font-bold mb-6 text-white'}>{content?.ctaTitle}</h2>
<p className={content?.ctaDescriptionStyleClass || 'text-xl mb-8 max-w-2xl mx-auto'}>{content?.ctaDescription}</p>
<Link
to={ROUTES_ENUM.public.contact}
className="bg-white text-blue-900 px-8 py-3 rounded-lg font-semibold hover:bg-blue-50 transition-colors"
className={content?.ctaButtonStyleClass || 'bg-white text-blue-900 px-8 py-3 rounded-lg font-semibold hover:bg-blue-50 transition-colors'}
>
{content?.ctaButtonLabel}
</Link>

View file

@ -7,6 +7,14 @@ const SAFELIST_COLORS = 'colors'
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}', './safelist.txt'],
safelist: [
{
// Covers most utility groups used by designer-entered class strings.
pattern:
/^(container|block|inline-block|inline-flex|hidden|absolute|relative|fixed|sticky|top-|right-|bottom-|left-|z-|w-|h-|min-w-|min-h-|max-w-|max-h-|m-|mx-|my-|mt-|mr-|mb-|ml-|p-|px-|py-|pt-|pr-|pb-|pl-|space-x-|space-y-|gap-|grid|grid-cols-|col-span-|row-span-|flex|flex-col|flex-row|flex-wrap|items-|justify-|self-|content-|overflow-|truncate|rounded|rounded-|border|border-|shadow|shadow-|text-|font-|leading-|tracking-|uppercase|lowercase|capitalize|bg-|from-|via-|to-|opacity-|backdrop-blur|transition|duration-|ease-|transform|scale-|rotate-|translate-|cursor-|select-|pointer-events-|ring-|outline-|whitespace-|break-|object-|aspect-|prose|prose-.*)$/,
variants: ['hover', 'focus', 'active', 'dark', 'sm', 'md', 'lg', 'xl', '2xl'],
},
],
darkMode: 'class',
theme: {
extend: {