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 TitleKey { get; set; }
public string SubtitleKey { get; set; } public string SubtitleKey { get; set; }
public string StyleClass { get; set; }
public List<HomeSlideServiceDto> Services { get; set; } = []; public List<HomeSlideServiceDto> Services { get; set; } = [];
} }
@ -74,6 +75,7 @@ public class HomeSlideServiceDto
public string Icon { get; set; } public string Icon { get; set; }
public string TitleKey { get; set; } public string TitleKey { get; set; }
public string DescriptionKey { get; set; } public string DescriptionKey { get; set; }
public string StyleClass { get; set; }
} }
public class HomeFeatureDto public class HomeFeatureDto
@ -81,6 +83,7 @@ public class HomeFeatureDto
public string Icon { get; set; } public string Icon { get; set; }
public string TitleKey { get; set; } public string TitleKey { get; set; }
public string DescriptionKey { get; set; } public string DescriptionKey { get; set; }
public string StyleClass { get; set; }
} }
public class HomeSolutionDto public class HomeSolutionDto
@ -89,4 +92,5 @@ public class HomeSolutionDto
public string ColorClass { get; set; } public string ColorClass { get; set; }
public string TitleKey { get; set; } public string TitleKey { get; set; }
public string DescriptionKey { 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<SaveAboutStatInput> Stats { get; set; } = [];
public List<SaveLocalizedTextInput> Descriptions { get; set; } = []; public List<SaveLocalizedTextInput> Descriptions { get; set; } = [];
public List<SaveAboutSectionInput> Sections { get; set; } = []; public List<SaveAboutSectionInput> Sections { get; set; } = [];
public List<SaveLocalizedTextInput> StyleTexts { get; set; } = [];
} }
public class SaveAboutStatInput public class SaveAboutStatInput
@ -58,6 +59,7 @@ public class SaveServicesPageInput
public string CtaButtonLabelValue { get; set; } public string CtaButtonLabelValue { get; set; }
public List<SaveServiceItemInput> ServiceItems { get; set; } = []; public List<SaveServiceItemInput> ServiceItems { get; set; } = [];
public List<SaveServiceItemInput> SupportItems { get; set; } = []; public List<SaveServiceItemInput> SupportItems { get; set; } = [];
public List<SaveLocalizedTextInput> StyleTexts { get; set; } = [];
} }
public class SaveServiceItemInput public class SaveServiceItemInput
@ -84,22 +86,40 @@ public class SaveHomePageInput
public string HeroBackgroundImageValue { get; set; } public string HeroBackgroundImageValue { get; set; }
public string HeroPrimaryCtaKey { get; set; } public string HeroPrimaryCtaKey { get; set; }
public string HeroPrimaryCtaValue { get; set; } public string HeroPrimaryCtaValue { get; set; }
public string HeroPrimaryCtaStyleKey { get; set; }
public string HeroPrimaryCtaStyleValue { get; set; }
public string HeroSecondaryCtaKey { get; set; } public string HeroSecondaryCtaKey { get; set; }
public string HeroSecondaryCtaValue { get; set; } public string HeroSecondaryCtaValue { get; set; }
public string HeroSecondaryCtaStyleKey { get; set; }
public string HeroSecondaryCtaStyleValue { get; set; }
public string FeaturesTitleKey { get; set; } public string FeaturesTitleKey { get; set; }
public string FeaturesTitleValue { get; set; } public string FeaturesTitleValue { get; set; }
public string FeaturesTitleStyleKey { get; set; }
public string FeaturesTitleStyleValue { get; set; }
public string FeaturesSubtitleKey { get; set; } public string FeaturesSubtitleKey { get; set; }
public string FeaturesSubtitleValue { get; set; } public string FeaturesSubtitleValue { get; set; }
public string FeaturesSubtitleStyleKey { get; set; }
public string FeaturesSubtitleStyleValue { get; set; }
public string SolutionsTitleKey { get; set; } public string SolutionsTitleKey { get; set; }
public string SolutionsTitleValue { get; set; } public string SolutionsTitleValue { get; set; }
public string SolutionsTitleStyleKey { get; set; }
public string SolutionsTitleStyleValue { get; set; }
public string SolutionsSubtitleKey { get; set; } public string SolutionsSubtitleKey { get; set; }
public string SolutionsSubtitleValue { get; set; } public string SolutionsSubtitleValue { get; set; }
public string SolutionsSubtitleStyleKey { get; set; }
public string SolutionsSubtitleStyleValue { get; set; }
public string CtaTitleKey { get; set; } public string CtaTitleKey { get; set; }
public string CtaTitleValue { get; set; } public string CtaTitleValue { get; set; }
public string CtaTitleStyleKey { get; set; }
public string CtaTitleStyleValue { get; set; }
public string CtaSubtitleKey { get; set; } public string CtaSubtitleKey { get; set; }
public string CtaSubtitleValue { get; set; } public string CtaSubtitleValue { get; set; }
public string CtaSubtitleStyleKey { get; set; }
public string CtaSubtitleStyleValue { get; set; }
public string CtaButtonLabelKey { get; set; } public string CtaButtonLabelKey { get; set; }
public string CtaButtonLabelValue { 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<SaveHomeSlideInput> Slides { get; set; } = [];
public List<SaveHomeFeatureInput> Features { get; set; } = []; public List<SaveHomeFeatureInput> Features { get; set; } = [];
public List<SaveHomeSolutionInput> Solutions { get; set; } = []; public List<SaveHomeSolutionInput> Solutions { get; set; } = [];
@ -111,6 +131,7 @@ public class SaveHomeSlideInput
public string TitleValue { get; set; } public string TitleValue { get; set; }
public string SubtitleKey { get; set; } public string SubtitleKey { get; set; }
public string SubtitleValue { get; set; } public string SubtitleValue { get; set; }
public string StyleClass { get; set; }
public List<SaveHomeSlideServiceInput> Services { get; set; } = []; public List<SaveHomeSlideServiceInput> Services { get; set; } = [];
} }
@ -121,6 +142,7 @@ public class SaveHomeSlideServiceInput
public string TitleValue { get; set; } public string TitleValue { get; set; }
public string DescriptionKey { get; set; } public string DescriptionKey { get; set; }
public string DescriptionValue { get; set; } public string DescriptionValue { get; set; }
public string StyleClass { get; set; }
} }
public class SaveHomeFeatureInput public class SaveHomeFeatureInput
@ -130,6 +152,7 @@ public class SaveHomeFeatureInput
public string TitleValue { get; set; } public string TitleValue { get; set; }
public string DescriptionKey { get; set; } public string DescriptionKey { get; set; }
public string DescriptionValue { get; set; } public string DescriptionValue { get; set; }
public string StyleClass { get; set; }
} }
public class SaveHomeSolutionInput public class SaveHomeSolutionInput
@ -140,6 +163,7 @@ public class SaveHomeSolutionInput
public string TitleValue { get; set; } public string TitleValue { get; set; }
public string DescriptionKey { get; set; } public string DescriptionKey { get; set; }
public string DescriptionValue { get; set; } public string DescriptionValue { get; set; }
public string StyleClass { get; set; }
} }
public class SaveContactPageInput public class SaveContactPageInput
@ -168,6 +192,7 @@ public class SaveContactPageInput
public string BankBranch { get; set; } public string BankBranch { get; set; }
public string BankAccountNumber { get; set; } public string BankAccountNumber { get; set; }
public string BankIban { get; set; } public string BankIban { get; set; }
public string BankStyleClass { get; set; }
public string WorkHoursTitleKey { get; set; } public string WorkHoursTitleKey { get; set; }
public string WorkHoursTitleValue { get; set; } public string WorkHoursTitleValue { get; set; }
@ -177,6 +202,7 @@ public class SaveContactPageInput
public string WorkWeekendValue { get; set; } public string WorkWeekendValue { get; set; }
public string WorkWhatsappKey { get; set; } public string WorkWhatsappKey { get; set; }
public string WorkWhatsappValue { get; set; } public string WorkWhatsappValue { get; set; }
public string WorkHoursStyleClass { get; set; }
public string MapTitleKey { get; set; } public string MapTitleKey { get; set; }
public string MapTitleValue { get; set; } public string MapTitleValue { get; set; }
@ -186,4 +212,7 @@ public class SaveContactPageInput
public bool MapAllowFullScreen { get; set; } public bool MapAllowFullScreen { get; set; }
public string MapLoading { get; set; } public string MapLoading { get; set; }
public string MapReferrerPolicy { 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); 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 _aboutRepository.UpdateAsync(entity, autoSave: true);
await _languageTextAppService.ClearRedisCacheAsync(); await _languageTextAppService.ClearRedisCacheAsync();
} }
@ -176,6 +181,11 @@ public class PublicAppService : PlatformAppService
await UpsertLanguageTextAsync(input.CultureName, input.CtaDescriptionKey, input.CtaDescriptionValue); await UpsertLanguageTextAsync(input.CultureName, input.CtaDescriptionKey, input.CtaDescriptionValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue); 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 CurrentUnitOfWork!.SaveChangesAsync();
await _languageTextAppService.ClearRedisCacheAsync(); await _languageTextAppService.ClearRedisCacheAsync();
} }
@ -212,11 +222,13 @@ public class PublicAppService : PlatformAppService
{ {
TitleKey = slide.TitleKey, TitleKey = slide.TitleKey,
SubtitleKey = slide.SubtitleKey, SubtitleKey = slide.SubtitleKey,
StyleClass = slide.StyleClass,
Services = slide.Services.Select(service => new HomeSlideServiceDto Services = slide.Services.Select(service => new HomeSlideServiceDto
{ {
Icon = service.Icon, Icon = service.Icon,
TitleKey = service.TitleKey, TitleKey = service.TitleKey,
DescriptionKey = service.DescriptionKey, DescriptionKey = service.DescriptionKey,
StyleClass = service.StyleClass,
}).ToList(), }).ToList(),
}).ToList()); }).ToList());
@ -225,6 +237,7 @@ public class PublicAppService : PlatformAppService
Icon = feature.Icon, Icon = feature.Icon,
TitleKey = feature.TitleKey, TitleKey = feature.TitleKey,
DescriptionKey = feature.DescriptionKey, DescriptionKey = feature.DescriptionKey,
StyleClass = feature.StyleClass,
}).ToList()); }).ToList());
entity.SolutionsJson = JsonSerializer.Serialize(input.Solutions.Select(solution => new HomeSolutionDto entity.SolutionsJson = JsonSerializer.Serialize(input.Solutions.Select(solution => new HomeSolutionDto
@ -233,6 +246,7 @@ public class PublicAppService : PlatformAppService
ColorClass = solution.ColorClass, ColorClass = solution.ColorClass,
TitleKey = solution.TitleKey, TitleKey = solution.TitleKey,
DescriptionKey = solution.DescriptionKey, DescriptionKey = solution.DescriptionKey,
StyleClass = solution.StyleClass,
}).ToList()); }).ToList());
if (isNewEntity) if (isNewEntity)
@ -246,14 +260,23 @@ public class PublicAppService : PlatformAppService
await UpsertLanguageTextAsync(input.CultureName, input.HeroBackgroundImageKey, input.HeroBackgroundImageValue); await UpsertLanguageTextAsync(input.CultureName, input.HeroBackgroundImageKey, input.HeroBackgroundImageValue);
await UpsertLanguageTextAsync(input.CultureName, input.HeroPrimaryCtaKey, input.HeroPrimaryCtaValue); 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.HeroSecondaryCtaKey, input.HeroSecondaryCtaValue);
await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaStyleKey, input.HeroSecondaryCtaStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleKey, input.FeaturesTitleValue); 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.FeaturesSubtitleKey, input.FeaturesSubtitleValue);
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleStyleKey, input.FeaturesSubtitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleKey, input.SolutionsTitleValue); 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.SolutionsSubtitleKey, input.SolutionsSubtitleValue);
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleStyleKey, input.SolutionsSubtitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleKey, input.CtaTitleValue); 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.CtaSubtitleKey, input.CtaSubtitleValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleStyleKey, input.CtaSubtitleStyleValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue); await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue);
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonStyleKey, input.CtaButtonStyleValue);
foreach (var slide in input.Slides) foreach (var slide in input.Slides)
{ {
@ -528,6 +551,7 @@ public class PublicAppService : PlatformAppService
Branch = input.BankBranch, Branch = input.BankBranch,
AccountNumber = input.BankAccountNumber, AccountNumber = input.BankAccountNumber,
Iban = input.BankIban, Iban = input.BankIban,
StyleClass = input.BankStyleClass,
}); });
entity.WorkHoursJson = JsonSerializer.Serialize(new WorkHoursDto entity.WorkHoursJson = JsonSerializer.Serialize(new WorkHoursDto
@ -535,6 +559,7 @@ public class PublicAppService : PlatformAppService
Weekday = input.WorkWeekdayKey, Weekday = input.WorkWeekdayKey,
Weekend = input.WorkWeekendKey, Weekend = input.WorkWeekendKey,
Whatsapp = input.WorkWhatsappKey, Whatsapp = input.WorkWhatsappKey,
StyleClass = input.WorkHoursStyleClass,
}); });
entity.MapJson = JsonSerializer.Serialize(new MapDto entity.MapJson = JsonSerializer.Serialize(new MapDto
@ -546,6 +571,8 @@ public class PublicAppService : PlatformAppService
AllowFullScreen = input.MapAllowFullScreen, AllowFullScreen = input.MapAllowFullScreen,
Loading = input.MapLoading, Loading = input.MapLoading,
ReferrerPolicy = input.MapReferrerPolicy, ReferrerPolicy = input.MapReferrerPolicy,
ContainerStyleClass = input.MapContainerStyleClass,
FrameStyleClass = input.MapFrameStyleClass,
}); });
await _contactRepository.UpdateAsync(entity, autoSave: false); 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.WorkWhatsappKey, input.WorkWhatsappValue);
await UpsertLanguageTextAsync(input.CultureName, input.MapTitleKey, input.MapTitleValue); 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 CurrentUnitOfWork!.SaveChangesAsync();
await _languageTextAppService.ClearRedisCacheAsync(); await _languageTextAppService.ClearRedisCacheAsync();
} }

View file

@ -6,4 +6,5 @@ public class BankDto
public string Branch { get; set; } public string Branch { get; set; }
public string AccountNumber { get; set; } public string AccountNumber { get; set; }
public string Iban { 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 Width { get; set; }
public string Height { get; set; } public string Height { get; set; }
public string Style { get; set; } public string Style { get; set; }
public string ContainerStyleClass { get; set; }
public string FrameStyleClass { get; set; }
public bool? AllowFullScreen { get; set; } public bool? AllowFullScreen { get; set; }
public string Loading { get; set; } public string Loading { get; set; }
public string ReferrerPolicy { get; set; } public string ReferrerPolicy { get; set; }

View file

@ -5,4 +5,5 @@ public class WorkHoursDto
public string Weekday { get; set; } public string Weekday { get; set; }
public string Weekend { get; set; } public string Weekend { get; set; }
public string Whatsapp { 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 namespace Sozsoft.Platform.Migrations
{ {
[DbContext(typeof(PlatformDbContext))] [DbContext(typeof(PlatformDbContext))]
[Migration("20260317104139_Initial")] [Migration("20260317120000_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -25,6 +25,7 @@ interface HomeSlideServiceContent {
titleKey: string titleKey: string
description: string description: string
descriptionKey: string descriptionKey: string
styleClass: string
} }
interface HomeSlideContent { interface HomeSlideContent {
@ -32,6 +33,7 @@ interface HomeSlideContent {
titleKey: string titleKey: string
subtitle: string subtitle: string
subtitleKey: string subtitleKey: string
styleClass: string
services: HomeSlideServiceContent[] services: HomeSlideServiceContent[]
} }
@ -41,6 +43,7 @@ interface HomeFeatureContent {
titleKey: string titleKey: string
description: string description: string
descriptionKey: string descriptionKey: string
styleClass: string
} }
interface HomeSolutionContent { interface HomeSolutionContent {
@ -50,6 +53,7 @@ interface HomeSolutionContent {
titleKey: string titleKey: string
description: string description: string
descriptionKey: string descriptionKey: string
styleClass: string
} }
interface HomeContent { interface HomeContent {
@ -57,40 +61,75 @@ interface HomeContent {
heroBackgroundImageKey: string heroBackgroundImageKey: string
heroPrimaryCtaLabel: string heroPrimaryCtaLabel: string
heroPrimaryCtaKey: string heroPrimaryCtaKey: string
heroPrimaryCtaStyle: string
heroPrimaryCtaStyleKey: string
heroSecondaryCtaLabel: string heroSecondaryCtaLabel: string
heroSecondaryCtaKey: string heroSecondaryCtaKey: string
heroSecondaryCtaStyle: string
heroSecondaryCtaStyleKey: string
featuresTitle: string featuresTitle: string
featuresTitleKey: string featuresTitleKey: string
featuresTitleStyle: string
featuresTitleStyleKey: string
featuresSubtitle: string featuresSubtitle: string
featuresSubtitleKey: string featuresSubtitleKey: string
featuresSubtitleStyle: string
featuresSubtitleStyleKey: string
solutionsTitle: string solutionsTitle: string
solutionsTitleKey: string solutionsTitleKey: string
solutionsTitleStyle: string
solutionsTitleStyleKey: string
solutionsSubtitle: string solutionsSubtitle: string
solutionsSubtitleKey: string solutionsSubtitleKey: string
solutionsSubtitleStyle: string
solutionsSubtitleStyleKey: string
ctaTitle: string ctaTitle: string
ctaTitleKey: string ctaTitleKey: string
ctaTitleStyle: string
ctaTitleStyleKey: string
ctaSubtitle: string ctaSubtitle: string
ctaSubtitleKey: string ctaSubtitleKey: string
ctaSubtitleStyle: string
ctaSubtitleStyleKey: string
ctaButtonLabel: string ctaButtonLabel: string
ctaButtonLabelKey: string ctaButtonLabelKey: string
ctaButtonStyle: string
ctaButtonStyleKey: string
slides: HomeSlideContent[] slides: HomeSlideContent[]
features: HomeFeatureContent[] features: HomeFeatureContent[]
solutions: HomeSolutionContent[] solutions: HomeSolutionContent[]
} }
const HOME_HERO_BACKGROUND_KEY = 'Public.home.hero.backgroundImage'
const HOME_HERO_DEFAULT_IMAGE = const HOME_HERO_DEFAULT_IMAGE =
'https://images.pexels.com/photos/3183150/pexels-photo-3183150.jpeg?auto=compress&cs=tinysrgb&w=1920' '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_PRIMARY_CTA_STYLE_DEFAULT =
const HOME_HERO_SECONDARY_CTA_KEY = 'Public.hero.cta.discover' '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_FEATURES_TITLE_KEY = 'Public.features.title' const HOME_HERO_SECONDARY_CTA_STYLE_DEFAULT =
const HOME_FEATURES_SUBTITLE_KEY = 'Public.features.subtitle' '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_SOLUTIONS_TITLE_KEY = 'Public.solutions.title' const HOME_FEATURES_TITLE_STYLE_DEFAULT = 'text-4xl font-bold text-gray-900 mb-4'
const HOME_SOLUTIONS_SUBTITLE_KEY = 'Public.solutions.subtitle' const HOME_FEATURES_SUBTITLE_STYLE_DEFAULT = 'text-xl text-gray-600 max-w-2xl mx-auto'
const HOME_CTA_TITLE_KEY = 'Public.common.getStarted' const HOME_SOLUTIONS_TITLE_STYLE_DEFAULT = 'text-4xl font-bold text-gray-900 mb-4'
const HOME_CTA_SUBTITLE_KEY = 'Public.common.contact' const HOME_SOLUTIONS_SUBTITLE_STYLE_DEFAULT = 'text-xl text-gray-600 max-w-2xl mx-auto'
const HOME_CTA_BUTTON_KEY = 'Public.common.learnMore' 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) { function isLikelyLocalizationKey(value?: string) {
return Boolean(value && /^[A-Za-z0-9_.-]+$/.test(value) && value.includes('.')) return Boolean(value && /^[A-Za-z0-9_.-]+$/.test(value) && value.includes('.'))
@ -113,206 +152,121 @@ function resolveLocalizedValue(
return translatedValue === keyOrValue ? fallback || keyOrValue : translatedValue 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 { function buildHomeContent(home: HomeDto | undefined, translate: (key: string) => string): HomeContent {
const slideItems = home?.slidesDto?.length ? home.slidesDto : defaultSlides() const slideItems = home?.slidesDto || []
const featureItems = home?.featuresDto?.length ? home.featuresDto : defaultFeatures() const featureItems = home?.featuresDto || []
const solutionItems = home?.solutionsDto?.length ? home.solutionsDto : defaultSolutions() const solutionItems = home?.solutionsDto || []
return { return {
heroBackgroundImage: resolveLocalizedValue( heroBackgroundImage: resolveLocalizedValue(
translate, translate,
home?.heroBackgroundImageKey || HOME_HERO_BACKGROUND_KEY, home?.heroBackgroundImageKey,
HOME_HERO_DEFAULT_IMAGE, HOME_HERO_DEFAULT_IMAGE,
), ),
heroBackgroundImageKey: home?.heroBackgroundImageKey || HOME_HERO_BACKGROUND_KEY, heroBackgroundImageKey: home?.heroBackgroundImageKey || '',
heroPrimaryCtaLabel: resolveLocalizedValue( heroPrimaryCtaLabel: resolveLocalizedValue(translate, home?.heroPrimaryCtaKey, ''),
heroPrimaryCtaKey: home?.heroPrimaryCtaKey || '',
heroPrimaryCtaStyle: resolveLocalizedValue(
translate, 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, heroPrimaryCtaStyleKey: HOME_HERO_PRIMARY_CTA_STYLE_KEY,
heroSecondaryCtaLabel: resolveLocalizedValue( heroSecondaryCtaLabel: resolveLocalizedValue(translate, home?.heroSecondaryCtaKey, ''),
heroSecondaryCtaKey: home?.heroSecondaryCtaKey || '',
heroSecondaryCtaStyle: resolveLocalizedValue(
translate, 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, heroSecondaryCtaStyleKey: HOME_HERO_SECONDARY_CTA_STYLE_KEY,
featuresTitle: resolveLocalizedValue(translate, home?.featuresTitleKey || HOME_FEATURES_TITLE_KEY), featuresTitle: resolveLocalizedValue(translate, home?.featuresTitleKey, ''),
featuresTitleKey: home?.featuresTitleKey || HOME_FEATURES_TITLE_KEY, featuresTitleKey: home?.featuresTitleKey || '',
featuresSubtitle: resolveLocalizedValue( featuresTitleStyle: resolveLocalizedValue(
translate, translate,
home?.featuresSubtitleKey || HOME_FEATURES_SUBTITLE_KEY, HOME_FEATURES_TITLE_STYLE_KEY,
HOME_FEATURES_TITLE_STYLE_DEFAULT,
), ),
featuresSubtitleKey: home?.featuresSubtitleKey || HOME_FEATURES_SUBTITLE_KEY, featuresTitleStyleKey: HOME_FEATURES_TITLE_STYLE_KEY,
solutionsTitle: resolveLocalizedValue(translate, home?.solutionsTitleKey || HOME_SOLUTIONS_TITLE_KEY), featuresSubtitle: resolveLocalizedValue(translate, home?.featuresSubtitleKey, ''),
solutionsTitleKey: home?.solutionsTitleKey || HOME_SOLUTIONS_TITLE_KEY, featuresSubtitleKey: home?.featuresSubtitleKey || '',
solutionsSubtitle: resolveLocalizedValue( featuresSubtitleStyle: resolveLocalizedValue(
translate, translate,
home?.solutionsSubtitleKey || HOME_SOLUTIONS_SUBTITLE_KEY, HOME_FEATURES_SUBTITLE_STYLE_KEY,
HOME_FEATURES_SUBTITLE_STYLE_DEFAULT,
), ),
solutionsSubtitleKey: home?.solutionsSubtitleKey || HOME_SOLUTIONS_SUBTITLE_KEY, featuresSubtitleStyleKey: HOME_FEATURES_SUBTITLE_STYLE_KEY,
ctaTitle: resolveLocalizedValue(translate, home?.ctaTitleKey || HOME_CTA_TITLE_KEY), solutionsTitle: resolveLocalizedValue(translate, home?.solutionsTitleKey, ''),
ctaTitleKey: home?.ctaTitleKey || HOME_CTA_TITLE_KEY, solutionsTitleKey: home?.solutionsTitleKey || '',
ctaSubtitle: resolveLocalizedValue(translate, home?.ctaSubtitleKey || HOME_CTA_SUBTITLE_KEY), solutionsTitleStyle: resolveLocalizedValue(
ctaSubtitleKey: home?.ctaSubtitleKey || HOME_CTA_SUBTITLE_KEY, translate,
ctaButtonLabel: resolveLocalizedValue(translate, home?.ctaButtonLabelKey || HOME_CTA_BUTTON_KEY), HOME_SOLUTIONS_TITLE_STYLE_KEY,
ctaButtonLabelKey: home?.ctaButtonLabelKey || HOME_CTA_BUTTON_KEY, HOME_SOLUTIONS_TITLE_STYLE_DEFAULT,
slides: slideItems.map((slide, slideIndex) => ({ ),
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), 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), subtitle: resolveLocalizedValue(translate, slide.subtitleKey, slide.subtitleKey),
subtitleKey: slide.subtitleKey || `Public.home.dynamic.slide.${slideIndex + 1}.subtitle`, subtitleKey: slide.subtitleKey || '',
services: (slide.services || []).map((service, serviceIndex) => ({ styleClass: slide.styleClass || HOME_SLIDE_STYLE_DEFAULT,
icon: service.icon || 'FaCircle', services: (slide.services || []).map((service) => ({
icon: service.icon || '',
title: resolveLocalizedValue(translate, service.titleKey, service.titleKey), title: resolveLocalizedValue(translate, service.titleKey, service.titleKey),
titleKey: titleKey: service.titleKey || '',
service.titleKey ||
`Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.title`,
description: resolveLocalizedValue(translate, service.descriptionKey, service.descriptionKey), description: resolveLocalizedValue(translate, service.descriptionKey, service.descriptionKey),
descriptionKey: descriptionKey: service.descriptionKey || '',
service.descriptionKey || styleClass: service.styleClass || HOME_SLIDE_SERVICE_STYLE_DEFAULT,
`Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.description`,
})), })),
})), })),
features: featureItems.map((feature, index) => ({ features: featureItems.map((feature) => ({
icon: feature.icon || 'FaCircle', icon: feature.icon || '',
title: resolveLocalizedValue(translate, feature.titleKey, feature.titleKey), 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), 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) => ({ solutions: solutionItems.map((solution) => ({
icon: solution.icon || 'FaCircle', icon: solution.icon || '',
colorClass: solution.colorClass || 'bg-blue-600', colorClass: solution.colorClass || '',
title: resolveLocalizedValue(translate, solution.titleKey, solution.titleKey), 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), description: resolveLocalizedValue(translate, solution.descriptionKey, solution.descriptionKey),
descriptionKey: descriptionKey: solution.descriptionKey || '',
solution.descriptionKey || `Public.home.dynamic.solution.${index + 1}.description`, styleClass: solution.styleClass || HOME_SOLUTION_CARD_STYLE_DEFAULT,
})), })),
} }
} }
@ -428,14 +382,23 @@ const Home: React.FC = () => {
if ( if (
fieldKey === 'heroBackgroundImage' || fieldKey === 'heroBackgroundImage' ||
fieldKey === 'heroPrimaryCtaLabel' || fieldKey === 'heroPrimaryCtaLabel' ||
fieldKey === 'heroPrimaryCtaStyle' ||
fieldKey === 'heroSecondaryCtaLabel' || fieldKey === 'heroSecondaryCtaLabel' ||
fieldKey === 'heroSecondaryCtaStyle' ||
fieldKey === 'featuresTitle' || fieldKey === 'featuresTitle' ||
fieldKey === 'featuresTitleStyle' ||
fieldKey === 'featuresSubtitle' || fieldKey === 'featuresSubtitle' ||
fieldKey === 'featuresSubtitleStyle' ||
fieldKey === 'solutionsTitle' || fieldKey === 'solutionsTitle' ||
fieldKey === 'solutionsTitleStyle' ||
fieldKey === 'solutionsSubtitle' || fieldKey === 'solutionsSubtitle' ||
fieldKey === 'solutionsSubtitleStyle' ||
fieldKey === 'ctaTitle' || fieldKey === 'ctaTitle' ||
fieldKey === 'ctaTitleStyle' ||
fieldKey === 'ctaSubtitle' || fieldKey === 'ctaSubtitle' ||
fieldKey === 'ctaButtonLabel' fieldKey === 'ctaSubtitleStyle' ||
fieldKey === 'ctaButtonLabel' ||
fieldKey === 'ctaButtonStyle'
) { ) {
return { return {
...current, ...current,
@ -452,12 +415,12 @@ const Home: React.FC = () => {
const slides = [...current.slides] const slides = [...current.slides]
const target = { ...slides[index] } const target = { ...slides[index] }
if (fieldKey === 'title' || fieldKey === 'subtitle') { if (fieldKey === 'title' || fieldKey === 'subtitle' || fieldKey === 'styleClass') {
target[fieldKey] = nextValue target[fieldKey] = nextValue
} else if (fieldKey.startsWith('service-')) { } else if (fieldKey.startsWith('service-')) {
const parts = fieldKey.split('-') const parts = fieldKey.split('-')
const serviceIndex = Number(parts[1]) 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 services = [...target.services]
const service = { ...services[serviceIndex] } const service = { ...services[serviceIndex] }
service[serviceField] = nextValue service[serviceField] = nextValue
@ -480,7 +443,7 @@ const Home: React.FC = () => {
const features = [...current.features] const features = [...current.features]
const feature = { ...features[index] } const feature = { ...features[index] }
const key = fieldKey as 'icon' | 'title' | 'description' const key = fieldKey as 'icon' | 'title' | 'description' | 'styleClass'
feature[key] = nextValue feature[key] = nextValue
features[index] = feature features[index] = feature
@ -498,7 +461,7 @@ const Home: React.FC = () => {
const solutions = [...current.solutions] const solutions = [...current.solutions]
const solution = { ...solutions[index] } const solution = { ...solutions[index] }
const key = fieldKey as 'icon' | 'title' | 'description' | 'colorClass' const key = fieldKey as 'icon' | 'title' | 'description' | 'colorClass' | 'styleClass'
solution[key] = nextValue solution[key] = nextValue
solutions[index] = solution solutions[index] = solution
@ -535,12 +498,24 @@ const Home: React.FC = () => {
type: 'text', type: 'text',
value: content.heroPrimaryCtaLabel, value: content.heroPrimaryCtaLabel,
}, },
{
key: 'heroPrimaryCtaStyle',
label: content.heroPrimaryCtaStyleKey,
type: 'text',
value: content.heroPrimaryCtaStyle,
},
{ {
key: 'heroSecondaryCtaLabel', key: 'heroSecondaryCtaLabel',
label: content.heroSecondaryCtaKey, label: content.heroSecondaryCtaKey,
type: 'text', type: 'text',
value: content.heroSecondaryCtaLabel, value: content.heroSecondaryCtaLabel,
}, },
{
key: 'heroSecondaryCtaStyle',
label: content.heroSecondaryCtaStyleKey,
type: 'text',
value: content.heroSecondaryCtaStyle,
},
], ],
} }
} }
@ -570,6 +545,12 @@ const Home: React.FC = () => {
type: 'textarea', type: 'textarea',
value: slide.subtitle, value: slide.subtitle,
}, },
{
key: 'styleClass',
label: `Slide${index + 1}.StyleClass`,
type: 'text',
value: slide.styleClass,
},
...slide.services.flatMap((service, serviceIndex) => [ ...slide.services.flatMap((service, serviceIndex) => [
{ {
key: `service-${serviceIndex}-icon`, key: `service-${serviceIndex}-icon`,
@ -590,6 +571,12 @@ const Home: React.FC = () => {
type: 'textarea' as const, type: 'textarea' as const,
value: service.description, 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', type: 'text',
value: content.featuresTitle, value: content.featuresTitle,
}, },
{
key: 'featuresTitleStyle',
label: content.featuresTitleStyleKey,
type: 'text',
value: content.featuresTitleStyle,
},
{ {
key: 'featuresSubtitle', key: 'featuresSubtitle',
label: content.featuresSubtitleKey, label: content.featuresSubtitleKey,
type: 'textarea', type: 'textarea',
value: content.featuresSubtitle, value: content.featuresSubtitle,
}, },
{
key: 'featuresSubtitleStyle',
label: content.featuresSubtitleStyleKey,
type: 'text',
value: content.featuresSubtitleStyle,
},
], ],
} }
} }
@ -649,6 +648,12 @@ const Home: React.FC = () => {
type: 'textarea', type: 'textarea',
value: item.description, 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', type: 'text',
value: content.solutionsTitle, value: content.solutionsTitle,
}, },
{
key: 'solutionsTitleStyle',
label: content.solutionsTitleStyleKey,
type: 'text',
value: content.solutionsTitleStyle,
},
{ {
key: 'solutionsSubtitle', key: 'solutionsSubtitle',
label: content.solutionsSubtitleKey, label: content.solutionsSubtitleKey,
type: 'textarea', type: 'textarea',
value: content.solutionsSubtitle, value: content.solutionsSubtitle,
}, },
{
key: 'solutionsSubtitleStyle',
label: content.solutionsSubtitleStyleKey,
type: 'text',
value: content.solutionsSubtitleStyle,
},
], ],
} }
} }
@ -713,6 +730,12 @@ const Home: React.FC = () => {
type: 'textarea', type: 'textarea',
value: item.description, 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', type: 'text',
value: content.ctaTitle, value: content.ctaTitle,
}, },
{
key: 'ctaTitleStyle',
label: content.ctaTitleStyleKey,
type: 'text',
value: content.ctaTitleStyle,
},
{ {
key: 'ctaSubtitle', key: 'ctaSubtitle',
label: content.ctaSubtitleKey, label: content.ctaSubtitleKey,
type: 'textarea', type: 'textarea',
value: content.ctaSubtitle, value: content.ctaSubtitle,
}, },
{
key: 'ctaSubtitleStyle',
label: content.ctaSubtitleStyleKey,
type: 'text',
value: content.ctaSubtitleStyle,
},
{ {
key: 'ctaButtonLabel', key: 'ctaButtonLabel',
label: content.ctaButtonLabelKey, label: content.ctaButtonLabelKey,
type: 'text', type: 'text',
value: content.ctaButtonLabel, value: content.ctaButtonLabel,
}, },
{
key: 'ctaButtonStyle',
label: content.ctaButtonStyleKey,
type: 'text',
value: content.ctaButtonStyle,
},
], ],
} }
} }
@ -762,54 +803,71 @@ const Home: React.FC = () => {
heroBackgroundImageValue: content.heroBackgroundImage, heroBackgroundImageValue: content.heroBackgroundImage,
heroPrimaryCtaKey: content.heroPrimaryCtaKey, heroPrimaryCtaKey: content.heroPrimaryCtaKey,
heroPrimaryCtaValue: content.heroPrimaryCtaLabel, heroPrimaryCtaValue: content.heroPrimaryCtaLabel,
heroPrimaryCtaStyleKey: content.heroPrimaryCtaStyleKey,
heroPrimaryCtaStyleValue: content.heroPrimaryCtaStyle,
heroSecondaryCtaKey: content.heroSecondaryCtaKey, heroSecondaryCtaKey: content.heroSecondaryCtaKey,
heroSecondaryCtaValue: content.heroSecondaryCtaLabel, heroSecondaryCtaValue: content.heroSecondaryCtaLabel,
heroSecondaryCtaStyleKey: content.heroSecondaryCtaStyleKey,
heroSecondaryCtaStyleValue: content.heroSecondaryCtaStyle,
featuresTitleKey: content.featuresTitleKey, featuresTitleKey: content.featuresTitleKey,
featuresTitleValue: content.featuresTitle, featuresTitleValue: content.featuresTitle,
featuresTitleStyleKey: content.featuresTitleStyleKey,
featuresTitleStyleValue: content.featuresTitleStyle,
featuresSubtitleKey: content.featuresSubtitleKey, featuresSubtitleKey: content.featuresSubtitleKey,
featuresSubtitleValue: content.featuresSubtitle, featuresSubtitleValue: content.featuresSubtitle,
featuresSubtitleStyleKey: content.featuresSubtitleStyleKey,
featuresSubtitleStyleValue: content.featuresSubtitleStyle,
solutionsTitleKey: content.solutionsTitleKey, solutionsTitleKey: content.solutionsTitleKey,
solutionsTitleValue: content.solutionsTitle, solutionsTitleValue: content.solutionsTitle,
solutionsTitleStyleKey: content.solutionsTitleStyleKey,
solutionsTitleStyleValue: content.solutionsTitleStyle,
solutionsSubtitleKey: content.solutionsSubtitleKey, solutionsSubtitleKey: content.solutionsSubtitleKey,
solutionsSubtitleValue: content.solutionsSubtitle, solutionsSubtitleValue: content.solutionsSubtitle,
solutionsSubtitleStyleKey: content.solutionsSubtitleStyleKey,
solutionsSubtitleStyleValue: content.solutionsSubtitleStyle,
ctaTitleKey: content.ctaTitleKey, ctaTitleKey: content.ctaTitleKey,
ctaTitleValue: content.ctaTitle, ctaTitleValue: content.ctaTitle,
ctaTitleStyleKey: content.ctaTitleStyleKey,
ctaTitleStyleValue: content.ctaTitleStyle,
ctaSubtitleKey: content.ctaSubtitleKey, ctaSubtitleKey: content.ctaSubtitleKey,
ctaSubtitleValue: content.ctaSubtitle, ctaSubtitleValue: content.ctaSubtitle,
ctaSubtitleStyleKey: content.ctaSubtitleStyleKey,
ctaSubtitleStyleValue: content.ctaSubtitleStyle,
ctaButtonLabelKey: content.ctaButtonLabelKey, ctaButtonLabelKey: content.ctaButtonLabelKey,
ctaButtonLabelValue: content.ctaButtonLabel, ctaButtonLabelValue: content.ctaButtonLabel,
ctaButtonStyleKey: content.ctaButtonStyleKey,
ctaButtonStyleValue: content.ctaButtonStyle,
slides: content.slides.map((slide, slideIndex) => ({ slides: content.slides.map((slide, slideIndex) => ({
titleKey: slide.titleKey || `Public.home.dynamic.slide.${slideIndex + 1}.title`, titleKey: slide.titleKey || '',
titleValue: slide.title, titleValue: slide.title,
subtitleKey: slide.subtitleKey || `Public.home.dynamic.slide.${slideIndex + 1}.subtitle`, subtitleKey: slide.subtitleKey || '',
subtitleValue: slide.subtitle, subtitleValue: slide.subtitle,
styleClass: slide.styleClass,
services: slide.services.map((service, serviceIndex) => ({ services: slide.services.map((service, serviceIndex) => ({
icon: service.icon, icon: service.icon,
titleKey: titleKey: service.titleKey || '',
service.titleKey ||
`Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.title`,
titleValue: service.title, titleValue: service.title,
descriptionKey: descriptionKey: service.descriptionKey || '',
service.descriptionKey ||
`Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.description`,
descriptionValue: service.description, descriptionValue: service.description,
styleClass: service.styleClass,
})), })),
})), })),
features: content.features.map((feature, index) => ({ features: content.features.map((feature, index) => ({
icon: feature.icon, icon: feature.icon,
titleKey: feature.titleKey || `Public.home.dynamic.feature.${index + 1}.title`, titleKey: feature.titleKey || '',
titleValue: feature.title, titleValue: feature.title,
descriptionKey: feature.descriptionKey || `Public.home.dynamic.feature.${index + 1}.description`, descriptionKey: feature.descriptionKey || '',
descriptionValue: feature.description, descriptionValue: feature.description,
styleClass: feature.styleClass,
})), })),
solutions: content.solutions.map((solution, index) => ({ solutions: content.solutions.map((solution, index) => ({
icon: solution.icon, icon: solution.icon,
colorClass: solution.colorClass, colorClass: solution.colorClass,
titleKey: solution.titleKey || `Public.home.dynamic.solution.${index + 1}.title`, titleKey: solution.titleKey || '',
titleValue: solution.title, titleValue: solution.title,
descriptionKey: descriptionKey: solution.descriptionKey || '',
solution.descriptionKey || `Public.home.dynamic.solution.${index + 1}.description`,
descriptionValue: solution.description, descriptionValue: solution.description,
styleClass: solution.styleClass,
})), })),
}) })
@ -918,7 +976,7 @@ const Home: React.FC = () => {
}} }}
className="h-full" 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"> <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"> <h1 className="text-3xl md:text-6xl font-bold mb-6 text-white animate-fade-in">
{slide.title} {slide.title}
@ -930,14 +988,14 @@ const Home: React.FC = () => {
<div className="flex flex-col md:flex-row justify-center gap-6 mb-16"> <div className="flex flex-col md:flex-row justify-center gap-6 mb-16">
<Link <Link
to={ROUTES_ENUM.public.contact} 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}{' '} {content?.heroPrimaryCtaLabel}{' '}
<FaArrowRight className="ml-2" size={20} /> <FaArrowRight className="ml-2" size={20} />
</Link> </Link>
<Link <Link
to={ROUTES_ENUM.public.products} 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} {content?.heroSecondaryCtaLabel}
</Link> </Link>
@ -947,7 +1005,10 @@ const Home: React.FC = () => {
{slide.services.map((service, i) => ( {slide.services.map((service, i) => (
<div <div
key={i} 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 || ''] const IconComponent = navigationIcon[service.icon || '']
@ -1029,10 +1090,10 @@ const Home: React.FC = () => {
onSelect={handleSelectBlock} onSelect={handleSelectBlock}
> >
<div className="text-center mb-16"> <div className="text-center mb-16">
<h2 className="text-4xl font-bold text-gray-900 mb-4"> <h2 className={content?.featuresTitleStyle}>
{content?.featuresTitle} {content?.featuresTitle}
</h2> </h2>
<p className="text-xl text-gray-600 max-w-2xl mx-auto"> <p className={content?.featuresSubtitleStyle}>
{content?.featuresSubtitle} {content?.featuresSubtitle}
</p> </p>
</div> </div>
@ -1047,7 +1108,12 @@ const Home: React.FC = () => {
isDesignMode={isDesignMode} isDesignMode={isDesignMode}
onSelect={handleSelectBlock} 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"> <div className="mb-6">
{(() => { {(() => {
const IconComponent = navigationIcon[feature.icon || ''] const IconComponent = navigationIcon[feature.icon || '']
@ -1073,10 +1139,10 @@ const Home: React.FC = () => {
onSelect={handleSelectBlock} onSelect={handleSelectBlock}
> >
<div className="text-center mb-16"> <div className="text-center mb-16">
<h2 className="text-4xl font-bold text-gray-900 mb-4"> <h2 className={content?.solutionsTitleStyle}>
{content?.solutionsTitle} {content?.solutionsTitle}
</h2> </h2>
<p className="text-xl text-gray-600 max-w-2xl mx-auto"> <p className={content?.solutionsSubtitleStyle}>
{content?.solutionsSubtitle} {content?.solutionsSubtitle}
</p> </p>
</div> </div>
@ -1091,7 +1157,7 @@ const Home: React.FC = () => {
isDesignMode={isDesignMode} isDesignMode={isDesignMode}
onSelect={handleSelectBlock} 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"> <div className="mb-6">
{(() => { {(() => {
const IconComponent = navigationIcon[s.icon || ''] const IconComponent = navigationIcon[s.icon || '']
@ -1116,11 +1182,11 @@ const Home: React.FC = () => {
> >
<section className="bg-blue-600 py-16"> <section className="bg-blue-600 py-16">
<div className="container mx-auto px-4 text-center"> <div className="container mx-auto px-4 text-center">
<h2 className="text-3xl font-bold text-white mb-4">{content?.ctaTitle}</h2> <h2 className={content?.ctaTitleStyle}>{content?.ctaTitle}</h2>
<p className="text-white text-lg mb-8">{content?.ctaSubtitle}</p> <p className={content?.ctaSubtitleStyle}>{content?.ctaSubtitle}</p>
<Link <Link
to={ROUTES_ENUM.public.contact} 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} {content?.ctaButtonLabel}
</Link> </Link>

View file

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

View file

@ -7,6 +7,14 @@ const SAFELIST_COLORS = 'colors'
export default { export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}', './safelist.txt'], 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', darkMode: 'class',
theme: { theme: {
extend: { extend: {