From 42f20c224f47a5e2a5b9b33011756feb176f9aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:27:30 +0300 Subject: [PATCH] =?UTF-8?q?Home=20ve=20Contact=20Designer=20komponenti=20t?= =?UTF-8?q?asarland=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Public/HomeDto.cs | 92 ++ .../Public/SavePublicPageContentInput.cs | 111 ++ .../Public/PublicAppService.cs | 237 ++++ .../Public/PublicAutoMapperProfile.cs | 1 + .../Seeds/ListFormSeeder_Saas.cs | 232 ---- .../Seeds/MenusData.json | 34 +- .../Seeds/PermissionsData.json | 64 +- .../Enums/TableNameEnum.cs | 1 + .../TableNameResolver.cs | 1 + .../Tenant/Administration/Website/Home.cs | 21 + .../EntityFrameworkCore/PlatformDbContext.cs | 21 + ....cs => 20260317104139_Initial.Designer.cs} | 91 +- ...4_Initial.cs => 20260317104139_Initial.cs} | 34 + .../PlatformDbContextModelSnapshot.cs | 89 ++ ui/src/services/contact.service.ts | 51 + ui/src/services/home.service.ts | 124 ++ ui/src/views/public/Contact.tsx | 753 +++++++++-- ui/src/views/public/Home.tsx | 1141 ++++++++++++++--- .../views/public/designer/SelectableBlock.tsx | 5 +- 19 files changed, 2502 insertions(+), 601 deletions(-) create mode 100644 api/src/Sozsoft.Platform.Application.Contracts/Public/HomeDto.cs create mode 100644 api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Home.cs rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260315193224_Initial.Designer.cs => 20260317104139_Initial.Designer.cs} (98%) rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260315193224_Initial.cs => 20260317104139_Initial.cs} (98%) create mode 100644 ui/src/services/home.service.ts diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Public/HomeDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Public/HomeDto.cs new file mode 100644 index 0000000..893bada --- /dev/null +++ b/api/src/Sozsoft.Platform.Application.Contracts/Public/HomeDto.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using Volo.Abp.Application.Dtos; + +namespace Sozsoft.Platform.Public; + +public class HomeDto : EntityDto +{ + public string HeroBackgroundImageKey { get; set; } + public string HeroPrimaryCtaKey { get; set; } + public string HeroSecondaryCtaKey { get; set; } + public string FeaturesTitleKey { get; set; } + public string FeaturesSubtitleKey { get; set; } + public string SolutionsTitleKey { get; set; } + public string SolutionsSubtitleKey { get; set; } + public string CtaTitleKey { get; set; } + public string CtaSubtitleKey { get; set; } + public string CtaButtonLabelKey { get; set; } + + [JsonIgnore] + public string SlidesJson { get; set; } + + public List SlidesDto + { + get + { + if (!string.IsNullOrEmpty(SlidesJson)) + return JsonSerializer.Deserialize>(SlidesJson); + return []; + } + set { SlidesJson = JsonSerializer.Serialize(value); } + } + + [JsonIgnore] + public string FeaturesJson { get; set; } + + public List FeaturesDto + { + get + { + if (!string.IsNullOrEmpty(FeaturesJson)) + return JsonSerializer.Deserialize>(FeaturesJson); + return []; + } + set { FeaturesJson = JsonSerializer.Serialize(value); } + } + + [JsonIgnore] + public string SolutionsJson { get; set; } + + public List SolutionsDto + { + get + { + if (!string.IsNullOrEmpty(SolutionsJson)) + return JsonSerializer.Deserialize>(SolutionsJson); + return []; + } + set { SolutionsJson = JsonSerializer.Serialize(value); } + } +} + +public class HomeSlideDto +{ + public string TitleKey { get; set; } + public string SubtitleKey { get; set; } + public List Services { get; set; } = []; +} + +public class HomeSlideServiceDto +{ + public string Icon { get; set; } + public string TitleKey { get; set; } + public string DescriptionKey { get; set; } +} + +public class HomeFeatureDto +{ + public string Icon { get; set; } + public string TitleKey { get; set; } + public string DescriptionKey { get; set; } +} + +public class HomeSolutionDto +{ + public string Icon { get; set; } + public string ColorClass { get; set; } + public string TitleKey { get; set; } + public string DescriptionKey { get; set; } +} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Public/SavePublicPageContentInput.cs b/api/src/Sozsoft.Platform.Application.Contracts/Public/SavePublicPageContentInput.cs index 85178d3..34344b0 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/Public/SavePublicPageContentInput.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/Public/SavePublicPageContentInput.cs @@ -75,4 +75,115 @@ public class SaveLocalizedTextInput { public string Key { get; set; } public string Value { get; set; } +} + +public class SaveHomePageInput +{ + public string CultureName { get; set; } + public string HeroBackgroundImageKey { get; set; } + public string HeroBackgroundImageValue { get; set; } + public string HeroPrimaryCtaKey { get; set; } + public string HeroPrimaryCtaValue { get; set; } + public string HeroSecondaryCtaKey { get; set; } + public string HeroSecondaryCtaValue { get; set; } + public string FeaturesTitleKey { get; set; } + public string FeaturesTitleValue { get; set; } + public string FeaturesSubtitleKey { get; set; } + public string FeaturesSubtitleValue { get; set; } + public string SolutionsTitleKey { get; set; } + public string SolutionsTitleValue { get; set; } + public string SolutionsSubtitleKey { get; set; } + public string SolutionsSubtitleValue { get; set; } + public string CtaTitleKey { get; set; } + public string CtaTitleValue { get; set; } + public string CtaSubtitleKey { get; set; } + public string CtaSubtitleValue { get; set; } + public string CtaButtonLabelKey { get; set; } + public string CtaButtonLabelValue { get; set; } + public List Slides { get; set; } = []; + public List Features { get; set; } = []; + public List Solutions { get; set; } = []; +} + +public class SaveHomeSlideInput +{ + public string TitleKey { get; set; } + public string TitleValue { get; set; } + public string SubtitleKey { get; set; } + public string SubtitleValue { get; set; } + public List Services { get; set; } = []; +} + +public class SaveHomeSlideServiceInput +{ + public string Icon { get; set; } + public string TitleKey { get; set; } + public string TitleValue { get; set; } + public string DescriptionKey { get; set; } + public string DescriptionValue { get; set; } +} + +public class SaveHomeFeatureInput +{ + public string Icon { get; set; } + public string TitleKey { get; set; } + public string TitleValue { get; set; } + public string DescriptionKey { get; set; } + public string DescriptionValue { get; set; } +} + +public class SaveHomeSolutionInput +{ + public string Icon { get; set; } + public string ColorClass { get; set; } + public string TitleKey { get; set; } + public string TitleValue { get; set; } + public string DescriptionKey { get; set; } + public string DescriptionValue { get; set; } +} + +public class SaveContactPageInput +{ + public string CultureName { get; set; } + + public string HeroTitleKey { get; set; } + public string HeroTitleValue { get; set; } + public string HeroSubtitleKey { get; set; } + public string HeroSubtitleValue { get; set; } + public string HeroImageKey { get; set; } + public string HeroImageValue { get; set; } + + public string ContactInfoTitleKey { get; set; } + public string ContactInfoTitleValue { get; set; } + public string AddressKey { get; set; } + public string AddressValue { get; set; } + public string PhoneNumber { get; set; } + public string Email { get; set; } + public string Location { get; set; } + public string TaxNumber { get; set; } + + public string BankTitleKey { get; set; } + public string BankTitleValue { get; set; } + public string BankAccountHolder { get; set; } + public string BankBranch { get; set; } + public string BankAccountNumber { get; set; } + public string BankIban { get; set; } + + public string WorkHoursTitleKey { get; set; } + public string WorkHoursTitleValue { get; set; } + public string WorkWeekdayKey { get; set; } + public string WorkWeekdayValue { get; set; } + public string WorkWeekendKey { get; set; } + public string WorkWeekendValue { get; set; } + public string WorkWhatsappKey { get; set; } + public string WorkWhatsappValue { get; set; } + + public string MapTitleKey { get; set; } + public string MapTitleValue { get; set; } + public string MapSrc { get; set; } + public string MapWidth { get; set; } + public string MapHeight { get; set; } + public bool MapAllowFullScreen { get; set; } + public string MapLoading { get; set; } + public string MapReferrerPolicy { get; set; } } \ No newline at end of file diff --git a/api/src/Sozsoft.Platform.Application/Public/PublicAppService.cs b/api/src/Sozsoft.Platform.Application/Public/PublicAppService.cs index acbd24a..bdf34c2 100644 --- a/api/src/Sozsoft.Platform.Application/Public/PublicAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Public/PublicAppService.cs @@ -31,6 +31,7 @@ public class PublicAppService : PlatformAppService private readonly IRepository _installmentOptionRepository; private readonly IRepository _orderRepository; private readonly IRepository _aboutRepository; + private readonly IRepository _homeRepository; private readonly IRepository _contactRepository; private readonly IIdentityUserRepository _identityUserRepository; private readonly IRepository _languageKeyRepository; @@ -49,6 +50,7 @@ public class PublicAppService : PlatformAppService IRepository installmentOptionRepository, IRepository orderRepository, IRepository aboutRepository, + IRepository homeRepository, IRepository contactRepository, IIdentityUserRepository identityUserRepository, IRepository languageKeyRepository, @@ -67,6 +69,7 @@ public class PublicAppService : PlatformAppService _installmentOptionRepository = installmentOptionRepository; _orderRepository = orderRepository; _aboutRepository = aboutRepository; + _homeRepository = homeRepository; _contactRepository = contactRepository; _identityUserRepository = identityUserRepository; _languageKeyRepository = languageKeyRepository; @@ -177,6 +180,109 @@ public class PublicAppService : PlatformAppService await _languageTextAppService.ClearRedisCacheAsync(); } + public async Task GetHomeAsync() + { + var entity = await _homeRepository.FirstOrDefaultAsync(); + if (entity == null) + { + entity = await _homeRepository.InsertAsync(CreateDefaultHomeEntity(), autoSave: true); + } + + return ObjectMapper.Map(entity); + } + + public async Task SaveHomePageAsync(SaveHomePageInput input) + { + var entity = await _homeRepository.FirstOrDefaultAsync(); + var isNewEntity = entity == null; + entity ??= CreateDefaultHomeEntity(); + + entity.HeroBackgroundImageKey = input.HeroBackgroundImageKey; + entity.HeroPrimaryCtaKey = input.HeroPrimaryCtaKey; + entity.HeroSecondaryCtaKey = input.HeroSecondaryCtaKey; + entity.FeaturesTitleKey = input.FeaturesTitleKey; + entity.FeaturesSubtitleKey = input.FeaturesSubtitleKey; + entity.SolutionsTitleKey = input.SolutionsTitleKey; + entity.SolutionsSubtitleKey = input.SolutionsSubtitleKey; + entity.CtaTitleKey = input.CtaTitleKey; + entity.CtaSubtitleKey = input.CtaSubtitleKey; + entity.CtaButtonLabelKey = input.CtaButtonLabelKey; + + entity.SlidesJson = JsonSerializer.Serialize(input.Slides.Select(slide => new HomeSlideDto + { + TitleKey = slide.TitleKey, + SubtitleKey = slide.SubtitleKey, + Services = slide.Services.Select(service => new HomeSlideServiceDto + { + Icon = service.Icon, + TitleKey = service.TitleKey, + DescriptionKey = service.DescriptionKey, + }).ToList(), + }).ToList()); + + entity.FeaturesJson = JsonSerializer.Serialize(input.Features.Select(feature => new HomeFeatureDto + { + Icon = feature.Icon, + TitleKey = feature.TitleKey, + DescriptionKey = feature.DescriptionKey, + }).ToList()); + + entity.SolutionsJson = JsonSerializer.Serialize(input.Solutions.Select(solution => new HomeSolutionDto + { + Icon = solution.Icon, + ColorClass = solution.ColorClass, + TitleKey = solution.TitleKey, + DescriptionKey = solution.DescriptionKey, + }).ToList()); + + if (isNewEntity) + { + await _homeRepository.InsertAsync(entity, autoSave: false); + } + else + { + await _homeRepository.UpdateAsync(entity, autoSave: false); + } + + await UpsertLanguageTextAsync(input.CultureName, input.HeroBackgroundImageKey, input.HeroBackgroundImageValue); + await UpsertLanguageTextAsync(input.CultureName, input.HeroPrimaryCtaKey, input.HeroPrimaryCtaValue); + await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaKey, input.HeroSecondaryCtaValue); + await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleKey, input.FeaturesTitleValue); + await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleKey, input.FeaturesSubtitleValue); + await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleKey, input.SolutionsTitleValue); + await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleKey, input.SolutionsSubtitleValue); + await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleKey, input.CtaTitleValue); + await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleKey, input.CtaSubtitleValue); + await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue); + + foreach (var slide in input.Slides) + { + await UpsertLanguageTextAsync(input.CultureName, slide.TitleKey, slide.TitleValue); + await UpsertLanguageTextAsync(input.CultureName, slide.SubtitleKey, slide.SubtitleValue); + + foreach (var service in slide.Services) + { + await UpsertLanguageTextAsync(input.CultureName, service.TitleKey, service.TitleValue); + await UpsertLanguageTextAsync(input.CultureName, service.DescriptionKey, service.DescriptionValue); + } + } + + foreach (var feature in input.Features) + { + await UpsertLanguageTextAsync(input.CultureName, feature.TitleKey, feature.TitleValue); + await UpsertLanguageTextAsync(input.CultureName, feature.DescriptionKey, feature.DescriptionValue); + } + + foreach (var solution in input.Solutions) + { + await UpsertLanguageTextAsync(input.CultureName, solution.TitleKey, solution.TitleValue); + await UpsertLanguageTextAsync(input.CultureName, solution.DescriptionKey, solution.DescriptionValue); + } + + await CurrentUnitOfWork!.SaveChangesAsync(); + await _languageTextAppService.ClearRedisCacheAsync(); + } + public async Task CreateDemoAsync(DemoDto input) { var demo = ObjectMapper.Map(input); @@ -406,6 +512,137 @@ public class PublicAppService : PlatformAppService return ObjectMapper.Map(entity); } + public async Task SaveContactPageAsync(SaveContactPageInput input) + { + var entity = await _contactRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Contact)); + + entity.Address = input.AddressKey; + entity.PhoneNumber = input.PhoneNumber; + entity.Email = input.Email; + entity.Location = input.Location; + entity.TaxNumber = long.TryParse(input.TaxNumber, out var taxNumber) ? taxNumber : null; + + entity.BankJson = JsonSerializer.Serialize(new BankDto + { + AccountHolder = input.BankAccountHolder, + Branch = input.BankBranch, + AccountNumber = input.BankAccountNumber, + Iban = input.BankIban, + }); + + entity.WorkHoursJson = JsonSerializer.Serialize(new WorkHoursDto + { + Weekday = input.WorkWeekdayKey, + Weekend = input.WorkWeekendKey, + Whatsapp = input.WorkWhatsappKey, + }); + + entity.MapJson = JsonSerializer.Serialize(new MapDto + { + Title = input.MapTitleKey, + Src = input.MapSrc, + Width = input.MapWidth, + Height = input.MapHeight, + AllowFullScreen = input.MapAllowFullScreen, + Loading = input.MapLoading, + ReferrerPolicy = input.MapReferrerPolicy, + }); + + await _contactRepository.UpdateAsync(entity, autoSave: false); + + await UpsertLanguageTextAsync(input.CultureName, input.HeroTitleKey, input.HeroTitleValue); + await UpsertLanguageTextAsync(input.CultureName, input.HeroSubtitleKey, input.HeroSubtitleValue); + await UpsertLanguageTextAsync(input.CultureName, input.HeroImageKey, input.HeroImageValue); + await UpsertLanguageTextAsync(input.CultureName, input.ContactInfoTitleKey, input.ContactInfoTitleValue); + await UpsertLanguageTextAsync(input.CultureName, input.AddressKey, input.AddressValue); + await UpsertLanguageTextAsync(input.CultureName, input.BankTitleKey, input.BankTitleValue); + await UpsertLanguageTextAsync(input.CultureName, input.WorkHoursTitleKey, input.WorkHoursTitleValue); + await UpsertLanguageTextAsync(input.CultureName, input.WorkWeekdayKey, input.WorkWeekdayValue); + await UpsertLanguageTextAsync(input.CultureName, input.WorkWeekendKey, input.WorkWeekendValue); + await UpsertLanguageTextAsync(input.CultureName, input.WorkWhatsappKey, input.WorkWhatsappValue); + await UpsertLanguageTextAsync(input.CultureName, input.MapTitleKey, input.MapTitleValue); + + await CurrentUnitOfWork!.SaveChangesAsync(); + await _languageTextAppService.ClearRedisCacheAsync(); + } + + private static Home CreateDefaultHomeEntity() + { + var slides = new List + { + new() + { + TitleKey = "Public.hero.slide1.title", + SubtitleKey = "Public.hero.slide1.subtitle", + Services = new List + { + new() { Icon = "FaCalendarAlt", TitleKey = "Public.hero.slide1.service1.title", DescriptionKey = "Public.hero.slide1.service1.desc" }, + new() { Icon = "FaUsers", TitleKey = "Public.hero.slide1.service2.title", DescriptionKey = "Public.hero.slide1.service2.desc" }, + new() { Icon = "FaShieldAlt", TitleKey = "Public.hero.slide1.service3.title", DescriptionKey = "Public.hero.slide1.service3.desc" }, + }, + }, + new() + { + TitleKey = "Public.hero.slide2.title", + SubtitleKey = "Public.hero.slide2.subtitle", + Services = new List + { + new() { Icon = "FaChartBar", TitleKey = "Public.hero.slide2.service1.title", DescriptionKey = "Public.hero.slide2.service1.desc" }, + new() { Icon = "FaCreditCard", TitleKey = "Public.hero.slide2.service2.title", DescriptionKey = "Public.hero.slide2.service2.desc" }, + new() { Icon = "FaDatabase", TitleKey = "Public.hero.slide2.service3.title", DescriptionKey = "Public.hero.slide2.service3.desc" }, + }, + }, + new() + { + TitleKey = "Public.hero.slide3.title", + SubtitleKey = "Public.hero.slide3.subtitle", + Services = new List + { + new() { Icon = "FaDesktop", TitleKey = "Public.hero.slide3.service1.title", DescriptionKey = "Public.hero.slide3.service1.desc" }, + new() { Icon = "FaServer", TitleKey = "Public.hero.slide3.service2.title", DescriptionKey = "Public.hero.slide3.service2.desc" }, + new() { Icon = "FaMobileAlt", TitleKey = "Public.hero.slide3.service3.title", DescriptionKey = "Public.hero.slide3.service3.desc" }, + }, + }, + }; + + var features = new List + { + new() { Icon = "FaUsers", TitleKey = "Public.features.reliable", DescriptionKey = "Public.features.reliable.desc" }, + new() { Icon = "FaCalendarAlt", TitleKey = "App.Coordinator.Classroom.Planning", DescriptionKey = "Public.features.rapid.desc" }, + new() { Icon = "FaBookOpen", TitleKey = "Public.features.expert", DescriptionKey = "Public.features.expert.desc" }, + new() { Icon = "FaCreditCard", TitleKey = "Public.features.muhasebe", DescriptionKey = "Public.features.muhasebe.desc" }, + new() { Icon = "FaRegComment", TitleKey = "Public.features.iletisim", DescriptionKey = "Public.features.iletisim.desc" }, + new() { Icon = "FaPhone", TitleKey = "Public.features.mobil", DescriptionKey = "Public.features.mobil.desc" }, + new() { Icon = "FaChartBar", TitleKey = "Public.features.scalable", DescriptionKey = "Public.features.scalable.desc" }, + new() { Icon = "FaShieldAlt", TitleKey = "Public.features.guvenlik", DescriptionKey = "Public.features.guvenlik.desc" }, + }; + + var solutions = new List + { + new() { Icon = "FaDesktop", ColorClass = "bg-blue-600", TitleKey = "Public.services.web.title", DescriptionKey = "Public.solutions.web.desc" }, + new() { Icon = "FaMobileAlt", ColorClass = "bg-purple-600", TitleKey = "Public.services.mobile.title", DescriptionKey = "Public.solutions.mobile.desc" }, + new() { Icon = "FaServer", ColorClass = "bg-green-600", TitleKey = "Public.solutions.custom.title", DescriptionKey = "Public.solutions.custom.desc" }, + new() { Icon = "FaDatabase", ColorClass = "bg-red-600", TitleKey = "Public.solutions.database.title", DescriptionKey = "Public.solutions.database.desc" }, + }; + + return new Home + { + HeroBackgroundImageKey = "Public.home.hero.backgroundImage", + HeroPrimaryCtaKey = "Public.hero.cta.consultation", + HeroSecondaryCtaKey = "Public.hero.cta.discover", + FeaturesTitleKey = "Public.features.title", + FeaturesSubtitleKey = "Public.features.subtitle", + SolutionsTitleKey = "Public.solutions.title", + SolutionsSubtitleKey = "Public.solutions.subtitle", + CtaTitleKey = "Public.common.getStarted", + CtaSubtitleKey = "Public.common.contact", + CtaButtonLabelKey = "Public.common.learnMore", + SlidesJson = JsonSerializer.Serialize(slides), + FeaturesJson = JsonSerializer.Serialize(features), + SolutionsJson = JsonSerializer.Serialize(solutions), + }; + } + private async Task UpsertLanguageTextAsync(string cultureName, string key, string value) { if (key.IsNullOrWhiteSpace()) diff --git a/api/src/Sozsoft.Platform.Application/Public/PublicAutoMapperProfile.cs b/api/src/Sozsoft.Platform.Application/Public/PublicAutoMapperProfile.cs index 3b828c1..7aa9f84 100644 --- a/api/src/Sozsoft.Platform.Application/Public/PublicAutoMapperProfile.cs +++ b/api/src/Sozsoft.Platform.Application/Public/PublicAutoMapperProfile.cs @@ -18,6 +18,7 @@ public class PublicAutoMapperProfile : Profile CreateMap(); CreateMap(); CreateMap(); + CreateMap(); CreateMap(); } } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs index ae39585..a293d7e 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs @@ -7385,237 +7385,5 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency } #endregion - - #region Contact - listFormName = AppCodes.Contact; - if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName)) - { - var listForm = await _listFormRepository.InsertAsync( - new ListForm - { - ListFormType = ListFormTypeEnum.List, - PageSize = 10, - ExportJson = DefaultExportJson, - IsSubForm = false, - ShowNote = true, - LayoutJson = DefaultLayoutJson(), - CultureName = LanguageCodes.En, - ListFormCode = listFormName, - Name = listFormName, - Title = listFormName, - DataSourceCode = SeedConsts.DataSources.DefaultCode, - IsTenant = false, - IsBranch = false, - IsOrganizationUnit = false, - Description = listFormName, - SelectCommandType = SelectCommandTypeEnum.Table, - SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Contact)), - KeyFieldName = "Id", - DefaultFilter = DefaultFilterJson, - KeyFieldDbSourceType = DbType.Guid, - SortMode = GridOptions.SortModeSingle, - FilterRowJson = DefaultFilterRowJson, - HeaderFilterJson = DefaultHeaderFilterJson, - SearchPanelJson = DefaultSearchPanelJson, - GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }), - SelectionJson = DefaultSelectionSingleJson, - ColumnOptionJson = DefaultColumnOptionJson(), - PermissionJson = DefaultPermissionJson(listFormName), - PagerOptionJson = DefaultPagerOptionJson, - EditingOptionJson = DefaultEditingOptionJson(listFormName, 800, 700, true, true, true, true, false), - EditingFormJson = JsonSerializer.Serialize(new List - { - new() { - Order = 1, ColCount = 1, ColSpan = 1, ItemType = "group", Items = - [ - new EditingFormItemDto { Order = 1, DataField = "Address", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxAutocomplete, EditorOptions=EditorOptionValues.ShowClearButton }, - new EditingFormItemDto { Order = 2, DataField = "PhoneNumber", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextBox, EditorOptions=EditorOptionValues.PhoneEditorOptions }, - new EditingFormItemDto { Order = 3, DataField = "Email", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 4, DataField = "Location", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 5, DataField = "TaxNumber", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxNumberBox }, - new EditingFormItemDto { Order = 6, DataField = "BankJson", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextArea, EditorOptions="{\"height\":100}" }, - new EditingFormItemDto { Order = 7, DataField = "WorkHoursJson", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextArea, EditorOptions="{\"height\":100}" }, - new EditingFormItemDto { Order = 8, DataField = "MapJson", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextArea, EditorOptions="{\"height\":100}" }, - ] - } - }), - DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Contact)), - DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(), - InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(), - }); - - #region Contact Fields - await _listFormFieldRepository.InsertManyAsync([ - new() - { - ListFormCode = listForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.Guid, - FieldName = "Id", - CaptionName = "App.Listform.ListformField.Id", - Width = 100, - ListOrderNo = 1, - Visible = false, - IsActive = true, - IsDeleted = false, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(listForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }, - new() - { - ListFormCode = listForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.String, - FieldName = "Address", - CaptionName = "App.Listform.ListformField.Address", - Width = 400, - ListOrderNo = 2, - Visible = true, - IsActive = true, - IsDeleted = false, - AllowSearch = false, - LookupJson = JsonSerializer.Serialize(new LookupDto { - DataSourceType = UiLookupDataSourceTypeEnum.Query, - DisplayExpr = "Name", - ValueExpr = "Key", - LookupQuery = LookupQueryValues.LanguageKeyValues - }), - ValidationRuleJson = DefaultValidationRuleRequiredJson, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(listForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }, - new() - { - ListFormCode = listForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.String, - FieldName = "PhoneNumber", - CaptionName = "App.Listform.ListformField.PhoneNumber", - Width = 100, - ListOrderNo = 3, - Visible = true, - IsActive = true, - IsDeleted = false, - AllowSearch = true, - ValidationRuleJson = DefaultValidationRuleRequiredJson, - EditorOptions = EditorOptionValues.PhoneEditorOptions, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(listForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }, - new() - { - ListFormCode = listForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.String, - FieldName = "Email", - CaptionName = "App.Listform.ListformField.Email", - Width = 150, - ListOrderNo = 4, - Visible = true, - IsActive = true, - IsDeleted = false, - AllowSearch = true, - ValidationRuleJson = DefaultValidationRuleEmailRequiredJson, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(listForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }, - new() - { - ListFormCode = listForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.String, - FieldName = "Location", - CaptionName = "App.Listform.ListformField.Location", - Width = 100, - ListOrderNo = 5, - Visible = true, - IsActive = true, - IsDeleted = false, - AllowSearch = true, - ValidationRuleJson = DefaultValidationRuleRequiredJson, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(listForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }, - new() - { - ListFormCode = listForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.String, - FieldName = "TaxNumber", - CaptionName = "App.Listform.ListformField.TaxNumber", - Width = 100, - ListOrderNo = 6, - Visible = true, - IsActive = true, - IsDeleted = false, - AllowSearch = true, - ValidationRuleJson = DefaultValidationRuleRequiredJson, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(listForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }, - new() - { - ListFormCode = listForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.String, - FieldName = "BankJson", - CaptionName = "App.Listform.ListformField.BankJson", - Width = 450, - ListOrderNo = 7, - Visible = true, - IsActive = true, - IsDeleted = false, - AllowSearch = true, - ValidationRuleJson = DefaultValidationRuleRequiredJson, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(listForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }, - new() - { - ListFormCode = listForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.String, - FieldName = "WorkHoursJson", - CaptionName = "App.Listform.ListformField.WorkHoursJson", - Width = 350, - ListOrderNo = 8, - Visible = true, - IsActive = true, - IsDeleted = false, - AllowSearch = true, - ValidationRuleJson = DefaultValidationRuleRequiredJson, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(listForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }, - new() - { - ListFormCode = listForm.ListFormCode, - CultureName = LanguageCodes.En, - SourceDbType = DbType.String, - FieldName = "MapJson", - CaptionName = "App.Listform.ListformField.MapJson", - Width = 100, - ListOrderNo = 9, - Visible = false, - IsActive = true, - IsDeleted = false, - AllowSearch = true, - ValidationRuleJson = DefaultValidationRuleRequiredJson, - ColumnCustomizationJson = DefaultColumnCustomizationJson, - PermissionJson = DefaultFieldPermissionJson(listForm.Name), - PivotSettingsJson = DefaultPivotSettingsJson, - }, - ]); - #endregion - } - #endregion } } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json index 6cdae0f..8820868 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json @@ -372,11 +372,11 @@ "authority": [] }, { - "key": "servicesDesigner", - "path": "/admin/public/services/designer", - "componentPath": "@/views/public/Services", + "key": "homeDesigner", + "path": "/admin/public/home/designer", + "componentPath": "@/views/public/Home", "routeType": "protected", - "authority": ["App.Services"] + "authority": ["App.Home"] }, { "key": "aboutDesigner", @@ -384,6 +384,20 @@ "componentPath": "@/views/public/About", "routeType": "protected", "authority": ["App.About"] + }, + { + "key": "servicesDesigner", + "path": "/admin/public/services/designer", + "componentPath": "@/views/public/Services", + "routeType": "protected", + "authority": ["App.Services"] + }, + { + "key": "contactDesigner", + "path": "/admin/public/contact/designer", + "componentPath": "@/views/public/Contact", + "routeType": "protected", + "authority": ["App.Contact"] } ], "Menus": [ @@ -639,6 +653,16 @@ "RequiredPermissionName": null, "IsDisabled": false }, + { + "ParentCode": "App.Public", + "Code": "App.Home", + "DisplayName": "App.Home", + "Order": 0, + "Url": "/admin/public/home/designer", + "Icon": "FcHome", + "RequiredPermissionName": "App.Home", + "IsDisabled": false + }, { "ParentCode": "App.Public", "Code": "App.About", @@ -734,7 +758,7 @@ "Code": "App.Contact", "DisplayName": "App.Contact", "Order": 10, - "Url": "/admin/list/App.Contact", + "Url": "/admin/public/contact/designer", "Icon": "FcContacts", "RequiredPermissionName": "App.Contact", "IsDisabled": false diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json index c060a4f..f09f296 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json @@ -1550,7 +1550,15 @@ "MenuGroup": "Erp|Kurs" }, - + { + "GroupName": "App.Saas", + "Name": "App.Home", + "ParentName": null, + "DisplayName": "App.Home", + "IsEnabled": true, + "MultiTenancySide": 2, + "MenuGroup": "Erp|Kurs" + }, { "GroupName": "App.Saas", "Name": "App.About", @@ -2029,60 +2037,6 @@ "MultiTenancySide": 2, "MenuGroup": "Erp|Kurs" }, - { - "GroupName": "App.Saas", - "Name": "App.Contact.Create", - "ParentName": "App.Contact", - "DisplayName": "Create", - "IsEnabled": true, - "MultiTenancySide": 2, - "MenuGroup": "Erp|Kurs" - }, - { - "GroupName": "App.Saas", - "Name": "App.Contact.Update", - "ParentName": "App.Contact", - "DisplayName": "Update", - "IsEnabled": true, - "MultiTenancySide": 2, - "MenuGroup": "Erp|Kurs" - }, - { - "GroupName": "App.Saas", - "Name": "App.Contact.Delete", - "ParentName": "App.Contact", - "DisplayName": "Delete", - "IsEnabled": true, - "MultiTenancySide": 2, - "MenuGroup": "Erp|Kurs" - }, - { - "GroupName": "App.Saas", - "Name": "App.Contact.Export", - "ParentName": "App.Contact", - "DisplayName": "Export", - "IsEnabled": true, - "MultiTenancySide": 2, - "MenuGroup": "Erp|Kurs" - }, - { - "GroupName": "App.Saas", - "Name": "App.Contact.Import", - "ParentName": "App.Contact", - "DisplayName": "Import", - "IsEnabled": true, - "MultiTenancySide": 2, - "MenuGroup": "Erp|Kurs" - }, - { - "GroupName": "App.Saas", - "Name": "App.Contact.Note", - "ParentName": "App.Contact", - "DisplayName": "Note", - "IsEnabled": true, - "MultiTenancySide": 2, - "MenuGroup": "Erp|Kurs" - }, { "GroupName": "App.Saas", "Name": "App.Routes", diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs index 516fb4a..fd2a7a9 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs @@ -50,6 +50,7 @@ public enum TableNameEnum Uom, WorkHour, About, + Home, Service, Product, PaymentMethod, diff --git a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs index e875224..2e153b2 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs @@ -47,6 +47,7 @@ public static class TableNameResolver { nameof(TableNameEnum.ForumCategory), (TablePrefix.TenantByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.ForumTopic), (TablePrefix.TenantByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.ForumPost), (TablePrefix.TenantByName, MenuPrefix.Saas) }, + { nameof(TableNameEnum.Home), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.About), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.Service), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.Product), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Home.cs b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Home.cs new file mode 100644 index 0000000..77732a7 --- /dev/null +++ b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Website/Home.cs @@ -0,0 +1,21 @@ +using System; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Sozsoft.Platform.Entities; + +public class Home : FullAuditedEntity +{ + public string HeroBackgroundImageKey { get; set; } + public string HeroPrimaryCtaKey { get; set; } + public string HeroSecondaryCtaKey { get; set; } + public string FeaturesTitleKey { get; set; } + public string FeaturesSubtitleKey { get; set; } + public string SolutionsTitleKey { get; set; } + public string SolutionsSubtitleKey { get; set; } + public string CtaTitleKey { get; set; } + public string CtaSubtitleKey { get; set; } + public string CtaButtonLabelKey { get; set; } + public string SlidesJson { get; set; } + public string FeaturesJson { get; set; } + public string SolutionsJson { get; set; } +} diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs index e64317e..63f99a2 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs @@ -84,6 +84,7 @@ public class PlatformDbContext : public DbSet UserDelegations { get; set; } public DbSet Sessions { get; set; } public DbSet Abouts { get; set; } + public DbSet Homes { get; set; } public DbSet Services { get; set; } public DbSet Products { get; set; } public DbSet PaymentMethods { get; set; } @@ -777,6 +778,26 @@ public class PlatformDbContext : b.Property(x => x.SectionsJson).HasColumnType("text"); }); + builder.Entity(b => + { + b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Home)), Prefix.DbSchema); + b.ConfigureByConvention(); + + b.Property(x => x.HeroBackgroundImageKey).HasMaxLength(256); + b.Property(x => x.HeroPrimaryCtaKey).HasMaxLength(256); + b.Property(x => x.HeroSecondaryCtaKey).HasMaxLength(256); + b.Property(x => x.FeaturesTitleKey).HasMaxLength(256); + b.Property(x => x.FeaturesSubtitleKey).HasMaxLength(256); + b.Property(x => x.SolutionsTitleKey).HasMaxLength(256); + b.Property(x => x.SolutionsSubtitleKey).HasMaxLength(256); + b.Property(x => x.CtaTitleKey).HasMaxLength(256); + b.Property(x => x.CtaSubtitleKey).HasMaxLength(256); + b.Property(x => x.CtaButtonLabelKey).HasMaxLength(256); + b.Property(x => x.SlidesJson).HasColumnType("text"); + b.Property(x => x.FeaturesJson).HasColumnType("text"); + b.Property(x => x.SolutionsJson).HasColumnType("text"); + }); + builder.Entity(b => { b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Service)), Prefix.DbSchema); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260315193224_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260317104139_Initial.Designer.cs similarity index 98% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260315193224_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260317104139_Initial.Designer.cs index f125413..3a3e49e 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260315193224_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260317104139_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260315193224_Initial")] + [Migration("20260317104139_Initial")] partial class Initial { /// @@ -1933,6 +1933,95 @@ namespace Sozsoft.Platform.Migrations b.ToTable("Sas_T_GlobalSearch", (string)null); }); + modelBuilder.Entity("Sozsoft.Platform.Entities.Home", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("CtaButtonLabelKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("CtaSubtitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("CtaTitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("FeaturesJson") + .HasColumnType("text"); + + b.Property("FeaturesSubtitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("FeaturesTitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("HeroBackgroundImageKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("HeroPrimaryCtaKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("HeroSecondaryCtaKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("SlidesJson") + .HasColumnType("text"); + + b.Property("SolutionsJson") + .HasColumnType("text"); + + b.Property("SolutionsSubtitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("SolutionsTitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.ToTable("Sas_H_Home", (string)null); + }); + modelBuilder.Entity("Sozsoft.Platform.Entities.InstallmentOption", b => { b.Property("Id") diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260315193224_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260317104139_Initial.cs similarity index 98% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260315193224_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260317104139_Initial.cs index 476caeb..4c09488 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260315193224_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260317104139_Initial.cs @@ -972,6 +972,37 @@ namespace Sozsoft.Platform.Migrations table.PrimaryKey("PK_Sas_H_DynamicService", x => x.Id); }); + migrationBuilder.CreateTable( + name: "Sas_H_Home", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + HeroBackgroundImageKey = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + HeroPrimaryCtaKey = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + HeroSecondaryCtaKey = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + FeaturesTitleKey = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + FeaturesSubtitleKey = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + SolutionsTitleKey = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + SolutionsSubtitleKey = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + CtaTitleKey = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + CtaSubtitleKey = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + CtaButtonLabelKey = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + SlidesJson = table.Column(type: "text", nullable: true), + FeaturesJson = table.Column(type: "text", nullable: true), + SolutionsJson = table.Column(type: "text", nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Sas_H_Home", x => x.Id); + }); + migrationBuilder.CreateTable( name: "Sas_H_InstallmentOption", columns: table => new @@ -3046,6 +3077,9 @@ namespace Sozsoft.Platform.Migrations migrationBuilder.DropTable( name: "Sas_H_DynamicService"); + migrationBuilder.DropTable( + name: "Sas_H_Home"); + migrationBuilder.DropTable( name: "Sas_H_InstallmentOption"); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 19677b2..8b1d765 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -1930,6 +1930,95 @@ namespace Sozsoft.Platform.Migrations b.ToTable("Sas_T_GlobalSearch", (string)null); }); + modelBuilder.Entity("Sozsoft.Platform.Entities.Home", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("CtaButtonLabelKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("CtaSubtitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("CtaTitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("FeaturesJson") + .HasColumnType("text"); + + b.Property("FeaturesSubtitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("FeaturesTitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("HeroBackgroundImageKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("HeroPrimaryCtaKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("HeroSecondaryCtaKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("SlidesJson") + .HasColumnType("text"); + + b.Property("SolutionsJson") + .HasColumnType("text"); + + b.Property("SolutionsSubtitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("SolutionsTitleKey") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.ToTable("Sas_H_Home", (string)null); + }); + modelBuilder.Entity("Sozsoft.Platform.Entities.InstallmentOption", b => { b.Property("Id") diff --git a/ui/src/services/contact.service.ts b/ui/src/services/contact.service.ts index 1ab8a1e..429a7eb 100644 --- a/ui/src/services/contact.service.ts +++ b/ui/src/services/contact.service.ts @@ -10,3 +10,54 @@ export function getContact() { { apiName: 'Default' }, ) } + +export interface SaveContactPageInput extends Record { + cultureName: string + heroTitleKey: string + heroTitleValue: string + heroSubtitleKey: string + heroSubtitleValue: string + heroImageKey: string + heroImageValue: string + contactInfoTitleKey: string + contactInfoTitleValue: string + addressKey: string + addressValue: string + phoneNumber: string + email: string + location: string + taxNumber: string + bankTitleKey: string + bankTitleValue: string + bankAccountHolder: string + bankBranch: string + bankAccountNumber: string + bankIban: string + workHoursTitleKey: string + workHoursTitleValue: string + workWeekdayKey: string + workWeekdayValue: string + workWeekendKey: string + workWeekendValue: string + workWhatsappKey: string + workWhatsappValue: string + mapTitleKey: string + mapTitleValue: string + mapSrc: string + mapWidth: string + mapHeight: string + mapAllowFullScreen: boolean + mapLoading: string + mapReferrerPolicy: string +} + +export function saveContactPage(input: SaveContactPageInput) { + return apiService.fetchData( + { + method: 'POST', + url: '/api/app/public/save-contact-page', + data: input, + }, + { apiName: 'Default' }, + ) +} diff --git a/ui/src/services/home.service.ts b/ui/src/services/home.service.ts new file mode 100644 index 0000000..1f5fbd9 --- /dev/null +++ b/ui/src/services/home.service.ts @@ -0,0 +1,124 @@ +import apiService from './api.service' + +export interface HomeSlideServiceDto { + icon: string + titleKey: string + descriptionKey: string +} + +export interface HomeSlideDto { + titleKey: string + subtitleKey: string + services: HomeSlideServiceDto[] +} + +export interface HomeFeatureDto { + icon: string + titleKey: string + descriptionKey: string +} + +export interface HomeSolutionDto { + icon: string + colorClass: string + titleKey: string + descriptionKey: string +} + +export interface HomeDto { + id: string + heroBackgroundImageKey: string + heroPrimaryCtaKey: string + heroSecondaryCtaKey: string + featuresTitleKey: string + featuresSubtitleKey: string + solutionsTitleKey: string + solutionsSubtitleKey: string + ctaTitleKey: string + ctaSubtitleKey: string + ctaButtonLabelKey: string + slidesDto: HomeSlideDto[] + featuresDto: HomeFeatureDto[] + solutionsDto: HomeSolutionDto[] +} + +export interface SaveHomeSlideServiceInput { + icon: string + titleKey: string + titleValue: string + descriptionKey: string + descriptionValue: string +} + +export interface SaveHomeSlideInput { + titleKey: string + titleValue: string + subtitleKey: string + subtitleValue: string + services: SaveHomeSlideServiceInput[] +} + +export interface SaveHomeFeatureInput { + icon: string + titleKey: string + titleValue: string + descriptionKey: string + descriptionValue: string +} + +export interface SaveHomeSolutionInput { + icon: string + colorClass: string + titleKey: string + titleValue: string + descriptionKey: string + descriptionValue: string +} + +export interface SaveHomePageInput { + cultureName: string + heroBackgroundImageKey: string + heroBackgroundImageValue: string + heroPrimaryCtaKey: string + heroPrimaryCtaValue: string + heroSecondaryCtaKey: string + heroSecondaryCtaValue: string + featuresTitleKey: string + featuresTitleValue: string + featuresSubtitleKey: string + featuresSubtitleValue: string + solutionsTitleKey: string + solutionsTitleValue: string + solutionsSubtitleKey: string + solutionsSubtitleValue: string + ctaTitleKey: string + ctaTitleValue: string + ctaSubtitleKey: string + ctaSubtitleValue: string + ctaButtonLabelKey: string + ctaButtonLabelValue: string + slides: SaveHomeSlideInput[] + features: SaveHomeFeatureInput[] + solutions: SaveHomeSolutionInput[] +} + +export function getHome() { + return apiService.fetchData( + { + method: 'GET', + url: '/api/app/public/home', + }, + { apiName: 'Default' }, + ) +} + +export function saveHomePage(input: SaveHomePageInput) { + return apiService.fetchData( + { + method: 'POST', + url: '/api/app/public/save-home-page', + data: input, + }, + { apiName: 'Default' }, + ) +} diff --git a/ui/src/views/public/Contact.tsx b/ui/src/views/public/Contact.tsx index 69bd1b9..1af4fb2 100644 --- a/ui/src/views/public/Contact.tsx +++ b/ui/src/views/public/Contact.tsx @@ -1,9 +1,6 @@ import React, { useEffect, useState } from 'react' import { - FaMailBulk, FaPhone, - FaMapPin, - FaFileAlt, FaBuilding, FaCalendarAlt, FaCalendarCheck, @@ -15,33 +12,491 @@ import { import { useLocalization } from '@/utils/hooks/useLocalization' import { Helmet } from 'react-helmet' import { ContactDto } from '@/proxy/contact/models' -import { Loading } from '@/components/shared' -import { getContact } from '@/services/contact.service' +import Loading from '@/components/shared/Loading' +import { getContact, saveContactPage } from '@/services/contact.service' import { APP_NAME } from '@/constants/app.constant' +import { useStoreActions, useStoreState } from '@/store' +import DesignerDrawer from './designer/DesignerDrawer' +import SelectableBlock from './designer/SelectableBlock' +import { DesignerSelection } from './designer/types' +import { useDesignerState } from './designer/useDesignerState' + +interface ContactContent { + heroTitle: string + heroTitleKey: string + heroSubtitle: string + heroSubtitleKey: string + heroImage: string + heroImageKey: string + contactInfoTitle: string + contactInfoTitleKey: string + address: string + addressKey: string + phoneNumber: string + email: string + location: string + taxNumber: string + bankTitle: string + bankTitleKey: string + bankAccountHolder: string + bankBranch: string + bankAccountNumber: string + bankIban: string + workHoursTitle: string + workHoursTitleKey: string + workWeekday: string + workWeekdayKey: string + workWeekend: string + workWeekendKey: string + workWhatsapp: string + workWhatsappKey: string + mapTitle: string + mapTitleKey: string + mapSrc: string + mapWidth: string + mapHeight: string + mapAllowFullScreen: string + mapLoading: string + mapReferrerPolicy: string +} + +const CONTACT_HERO_TITLE_KEY = 'App.Contact' +const CONTACT_HERO_SUBTITLE_KEY = 'Public.contact.subtitle' +const CONTACT_HERO_IMAGE_KEY = 'Public.contact.heroImage' +const CONTACT_HERO_IMAGE_DEFAULT = + 'https://images.pexels.com/photos/3183171/pexels-photo-3183171.jpeg?auto=compress&cs=tinysrgb&w=1920' +const CONTACT_INFO_TITLE_KEY = 'Abp.Identity.User.UserInformation.ContactInformation' +const CONTACT_ADDRESS_KEY = 'Public.contact.address.full' +const CONTACT_BANK_TITLE_KEY = 'Public.contact.bank.title' +const CONTACT_WORK_HOURS_TITLE_KEY = 'App.Definitions.WorkHour' +const CONTACT_WORK_WEEKDAY_KEY = 'Public.contact.workHours.weekday' +const CONTACT_WORK_WEEKEND_KEY = 'Public.contact.workHours.weekend' +const CONTACT_WORK_WHATSAPP_KEY = 'Public.contact.workHours.whatsapp' +const CONTACT_MAP_TITLE_KEY = 'Public.contact.location' + +function isLikelyLocalizationKey(value?: string) { + return Boolean(value && /^[A-Za-z0-9_.-]+$/.test(value) && value.includes('.')) +} + +function resolveLocalizedValue( + translate: (key: string) => string, + keyOrValue: string | undefined, + fallback = '', +) { + if (!keyOrValue) { + return fallback + } + + if (!isLikelyLocalizationKey(keyOrValue)) { + return keyOrValue + } + + const translatedValue = translate('::' + keyOrValue) + return translatedValue === keyOrValue ? fallback || keyOrValue : translatedValue +} + +function buildContactContent( + contact: ContactDto | undefined, + translate: (key: string) => string, +): ContactContent { + return { + heroTitle: resolveLocalizedValue(translate, CONTACT_HERO_TITLE_KEY, 'Contact'), + heroTitleKey: CONTACT_HERO_TITLE_KEY, + heroSubtitle: resolveLocalizedValue(translate, CONTACT_HERO_SUBTITLE_KEY), + heroSubtitleKey: CONTACT_HERO_SUBTITLE_KEY, + heroImage: resolveLocalizedValue(translate, CONTACT_HERO_IMAGE_KEY, CONTACT_HERO_IMAGE_DEFAULT), + heroImageKey: CONTACT_HERO_IMAGE_KEY, + contactInfoTitle: resolveLocalizedValue(translate, CONTACT_INFO_TITLE_KEY), + contactInfoTitleKey: CONTACT_INFO_TITLE_KEY, + address: resolveLocalizedValue(translate, contact?.address || CONTACT_ADDRESS_KEY), + addressKey: contact?.address || CONTACT_ADDRESS_KEY, + phoneNumber: contact?.phoneNumber || '', + email: contact?.email || '', + location: contact?.location || '', + taxNumber: contact?.taxNumber || '', + bankTitle: resolveLocalizedValue(translate, CONTACT_BANK_TITLE_KEY), + bankTitleKey: CONTACT_BANK_TITLE_KEY, + bankAccountHolder: contact?.bankDto?.accountHolder || '', + bankBranch: contact?.bankDto?.branch || '', + bankAccountNumber: contact?.bankDto?.accountNumber || '', + bankIban: contact?.bankDto?.iban || '', + workHoursTitle: resolveLocalizedValue(translate, CONTACT_WORK_HOURS_TITLE_KEY), + workHoursTitleKey: CONTACT_WORK_HOURS_TITLE_KEY, + workWeekday: resolveLocalizedValue(translate, contact?.workHoursDto?.weekday || CONTACT_WORK_WEEKDAY_KEY), + workWeekdayKey: contact?.workHoursDto?.weekday || CONTACT_WORK_WEEKDAY_KEY, + workWeekend: resolveLocalizedValue(translate, contact?.workHoursDto?.weekend || CONTACT_WORK_WEEKEND_KEY), + workWeekendKey: contact?.workHoursDto?.weekend || CONTACT_WORK_WEEKEND_KEY, + workWhatsapp: resolveLocalizedValue( + translate, + contact?.workHoursDto?.whatsapp || CONTACT_WORK_WHATSAPP_KEY, + ), + workWhatsappKey: contact?.workHoursDto?.whatsapp || CONTACT_WORK_WHATSAPP_KEY, + mapTitle: resolveLocalizedValue(translate, contact?.mapDto?.title || CONTACT_MAP_TITLE_KEY), + mapTitleKey: contact?.mapDto?.title || CONTACT_MAP_TITLE_KEY, + mapSrc: contact?.mapDto?.src || '', + mapWidth: contact?.mapDto?.width || '100%', + mapHeight: contact?.mapDto?.height || '700', + mapAllowFullScreen: String(contact?.mapDto?.allowFullScreen ?? true), + mapLoading: contact?.mapDto?.loading || 'lazy', + mapReferrerPolicy: contact?.mapDto?.referrerPolicy || 'no-referrer-when-downgrade', + } +} const Contact: React.FC = () => { const { translate } = useLocalization() + const { setLang } = useStoreActions((actions) => actions.locale) + const { getConfig } = useStoreActions((actions) => actions.abpConfig) + const configCultureName = useStoreState( + (state) => state.abpConfig.config?.localization.currentCulture.cultureName, + ) + const localeCurrentLang = useStoreState((state) => state.locale?.currentLang) + const currentLanguage = configCultureName || localeCurrentLang || 'tr' + const abpLanguages = useStoreState((state) => state.abpConfig.config?.localization.languages) || [] + const languageOptions = abpLanguages + .filter((language) => Boolean(language.cultureName)) + .map((language) => { + const cultureName = language.cultureName || 'tr' + return { + key: cultureName.toLowerCase().split('-')[0], + cultureName, + displayName: language.displayName || cultureName, + } + }) + const languagesFromConfig = languageOptions.map((language) => language.key) + const editorLanguages = Array.from( + new Set((languagesFromConfig.length > 0 ? languagesFromConfig : [currentLanguage]).filter(Boolean)), + ) + const [loading, setLoading] = useState(true) + const [isSaving, setIsSaving] = useState(false) + const [isPanelVisible, setIsPanelVisible] = useState(true) const [contact, setContact] = useState() + const initialContent = !loading ? buildContactContent(contact, translate) : null + const { + content, + isDesignMode, + selectedBlockId, + selectedLanguage, + supportedLanguages, + setContent, + setSelectedBlockId, + resetContent, + } = useDesignerState('contact', initialContent, { + currentLanguage, + supportedLanguages: editorLanguages, + }) + useEffect(() => { setLoading(true) - - const fetchServices = async () => { + const fetchContact = async () => { try { const result = await getContact() - setContact(result.data) } catch (error) { - console.error('About alınırken hata oluştu:', error) + console.error('Contact alinirken hata olustu:', error) } finally { setLoading(false) } } - fetchServices() + fetchContact() }, []) + const updateContent = (updater: (current: ContactContent) => ContactContent) => { + setContent((current) => { + if (!current) { + return current + } + + return updater(current) + }) + } + + const handleFieldChange = (fieldKey: string, value: string | string[]) => { + updateContent((current) => ({ + ...current, + [fieldKey]: value as string, + })) + } + + const selectedSelection: DesignerSelection | null = React.useMemo(() => { + if (!content || !selectedBlockId) { + return null + } + + if (selectedBlockId === 'hero') { + return { + id: 'hero', + title: 'Public.contact.hero.*', + description: 'Hero basligi, alt basligi ve arka plan gorselini duzenleyin.', + fields: [ + { + key: 'heroTitle', + label: content.heroTitleKey, + type: 'text', + value: content.heroTitle, + }, + { + key: 'heroSubtitle', + label: content.heroSubtitleKey, + type: 'textarea', + value: content.heroSubtitle, + }, + { + key: 'heroImage', + label: content.heroImageKey, + type: 'image', + value: content.heroImage, + }, + ], + } + } + + if (selectedBlockId === 'contact-info') { + return { + id: selectedBlockId, + title: content.contactInfoTitleKey, + description: 'Iletisim bilgilerini duzenleyin.', + fields: [ + { + key: 'contactInfoTitle', + label: content.contactInfoTitleKey, + type: 'text', + value: content.contactInfoTitle, + }, + { + key: 'address', + label: content.addressKey, + type: 'textarea', + value: content.address, + }, + { + key: 'phoneNumber', + label: 'Public.contact.phone', + type: 'text', + value: content.phoneNumber, + }, + { + key: 'email', + label: 'Public.contact.email', + type: 'text', + value: content.email, + }, + { + key: 'location', + label: 'Public.contact.location.plain', + type: 'text', + value: content.location, + }, + { + key: 'taxNumber', + label: 'Public.contact.taxNumber', + type: 'text', + value: content.taxNumber, + }, + ], + } + } + + if (selectedBlockId === 'bank') { + return { + id: selectedBlockId, + title: content.bankTitleKey, + description: 'Banka bilgilerini duzenleyin.', + fields: [ + { + key: 'bankTitle', + label: content.bankTitleKey, + type: 'text', + value: content.bankTitle, + }, + { + key: 'bankAccountHolder', + label: 'Public.contact.bank.accountHolder', + type: 'text', + value: content.bankAccountHolder, + }, + { + key: 'bankBranch', + label: 'Public.contact.bank.branch', + type: 'text', + value: content.bankBranch, + }, + { + key: 'bankAccountNumber', + label: 'Public.contact.bank.accountNumber', + type: 'text', + value: content.bankAccountNumber, + }, + { + key: 'bankIban', + label: 'Public.contact.bank.iban', + type: 'text', + value: content.bankIban, + }, + ], + } + } + + if (selectedBlockId === 'work-hours') { + return { + id: selectedBlockId, + title: content.workHoursTitleKey, + description: 'Calisma saatleri metinlerini duzenleyin.', + fields: [ + { + key: 'workHoursTitle', + label: content.workHoursTitleKey, + type: 'text', + value: content.workHoursTitle, + }, + { + key: 'workWeekday', + label: content.workWeekdayKey, + type: 'text', + value: content.workWeekday, + }, + { + key: 'workWeekend', + label: content.workWeekendKey, + type: 'text', + value: content.workWeekend, + }, + { + key: 'workWhatsapp', + label: content.workWhatsappKey, + type: 'text', + value: content.workWhatsapp, + }, + ], + } + } + + if (selectedBlockId === 'map') { + return { + id: selectedBlockId, + title: content.mapTitleKey, + description: 'Harita basligi ve iframe ayarlarini duzenleyin.', + fields: [ + { + key: 'mapTitle', + label: content.mapTitleKey, + type: 'text', + value: content.mapTitle, + }, + { + key: 'mapSrc', + label: 'Public.contact.map.src', + type: 'textarea', + value: content.mapSrc, + }, + { + key: 'mapWidth', + label: 'Public.contact.map.width', + type: 'text', + value: content.mapWidth, + }, + { + key: 'mapHeight', + label: 'Public.contact.map.height', + type: 'text', + value: content.mapHeight, + }, + { + key: 'mapAllowFullScreen', + label: 'Public.contact.map.allowFullScreen', + type: 'text', + value: content.mapAllowFullScreen, + placeholder: 'true veya false', + }, + { + key: 'mapLoading', + label: 'Public.contact.map.loading', + type: 'text', + value: content.mapLoading, + }, + { + key: 'mapReferrerPolicy', + label: 'Public.contact.map.referrerPolicy', + type: 'text', + value: content.mapReferrerPolicy, + }, + ], + } + } + + return null + }, [content, selectedBlockId]) + + const handleSaveAndExit = async () => { + if (!content || isSaving) { + return + } + + setIsSaving(true) + + try { + await saveContactPage({ + cultureName: selectedLanguage, + heroTitleKey: content.heroTitleKey, + heroTitleValue: content.heroTitle, + heroSubtitleKey: content.heroSubtitleKey, + heroSubtitleValue: content.heroSubtitle, + heroImageKey: content.heroImageKey, + heroImageValue: content.heroImage, + contactInfoTitleKey: content.contactInfoTitleKey, + contactInfoTitleValue: content.contactInfoTitle, + addressKey: content.addressKey, + addressValue: content.address, + phoneNumber: content.phoneNumber, + email: content.email, + location: content.location, + taxNumber: content.taxNumber, + bankTitleKey: content.bankTitleKey, + bankTitleValue: content.bankTitle, + bankAccountHolder: content.bankAccountHolder, + bankBranch: content.bankBranch, + bankAccountNumber: content.bankAccountNumber, + bankIban: content.bankIban, + workHoursTitleKey: content.workHoursTitleKey, + workHoursTitleValue: content.workHoursTitle, + workWeekdayKey: content.workWeekdayKey, + workWeekdayValue: content.workWeekday, + workWeekendKey: content.workWeekendKey, + workWeekendValue: content.workWeekend, + workWhatsappKey: content.workWhatsappKey, + workWhatsappValue: content.workWhatsapp, + mapTitleKey: content.mapTitleKey, + mapTitleValue: content.mapTitle, + mapSrc: content.mapSrc, + mapWidth: content.mapWidth, + mapHeight: content.mapHeight, + mapAllowFullScreen: content.mapAllowFullScreen.toLowerCase() === 'true', + mapLoading: content.mapLoading, + mapReferrerPolicy: content.mapReferrerPolicy, + }) + + await getConfig(false) + setSelectedBlockId(null) + } catch (error) { + console.error('Contact tasarimi kaydedilemedi:', error) + } finally { + setIsSaving(false) + } + } + + const handleLanguageChange = (language: string) => { + setLang(language) + } + + const handleSelectBlock = (blockId: string) => { + setSelectedBlockId(blockId) + if (!isPanelVisible) { + setIsPanelVisible(true) + } + } + if (loading) { return (
@@ -53,153 +508,197 @@ const Contact: React.FC = () => { } return ( -
+
- {/* Hero Section */} -
-
-
-

- {translate('::App.Contact')} -

-

{translate('::Public.contact.subtitle')}

+ {isDesignMode && ( +
+ Contact designer aktif
-
+ )} + + {isDesignMode && !isPanelVisible && ( + + )} + + +
+
+
+

{content?.heroTitle}

+

{content?.heroSubtitle}

+
+
+
- {/* Stats Section */}
-
+
-
-

- {translate('::Abp.Identity.User.UserInformation.ContactInformation')} -

-
-
- -
-

{translate('::' + contact?.address)}

+ +
+

{content?.contactInfoTitle}

+
+
+ +
+

{content?.address}

+
-
-
- -
-

{contact?.phoneNumber}

+
+ +
+

{content?.phoneNumber}

+
-
-
- - -
- -
-

{contact?.location}

+
+ +
+

{content?.location}

+
-
-
- -
-

{contact?.taxNumber}

+
+ +
+

{content?.taxNumber}

+
-
+ -
-

- {translate('::Public.contact.bank.title')} -

-
- Enpara Logo -
-

- {contact?.bankDto.accountHolder} -

-

{contact?.bankDto.branch}

-

{contact?.bankDto.accountNumber}

-

{contact?.bankDto.iban}

+ +
+

{content?.bankTitle}

+
+ Enpara Logo +
+

{content?.bankAccountHolder}

+

{content?.bankBranch}

+

{content?.bankAccountNumber}

+

{content?.bankIban}

+
-
+ - {/* Bank Information */} -
-

- {translate('::App.Definitions.WorkHour')} -

-
-
- -

- {translate('::' + contact?.workHoursDto.weekday)} -

-
-
- -

- {translate('::' + contact?.workHoursDto.weekend)} -

-
-
- -

- {translate('::' + contact?.workHoursDto.whatsapp)} -

+ +
+

{content?.workHoursTitle}

+
+
+ +

{content?.workWeekday}

+
+
+ +

{content?.workWeekend}

+
+
+ +

{content?.workWhatsapp}

+
-
+
- {/* Map Section */} -
-

- {translate('::' + contact?.mapDto.title)} -

-
- + +
+

{content?.mapTitle}

+
+ +
-
+
+ + 0 + ? languageOptions + : supportedLanguages.map((language) => ({ + key: language, + cultureName: language, + displayName: language.toUpperCase(), + })) + } + onClose={() => setIsPanelVisible(false)} + onSave={handleSaveAndExit} + onLanguageChange={handleLanguageChange} + onReset={resetContent} + onFieldChange={handleFieldChange} + />
) } diff --git a/ui/src/views/public/Home.tsx b/ui/src/views/public/Home.tsx index 867c09f..7bcb335 100644 --- a/ui/src/views/public/Home.tsx +++ b/ui/src/views/public/Home.tsx @@ -1,19 +1,7 @@ -import React, { useState, useEffect } from 'react' +import React, { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { FaArrowRight, - FaCalendarAlt, - FaUsers, - FaShieldAlt, - FaDesktop, - FaMobileAlt, - FaServer, - FaDatabase, - FaChartBar, - FaBookOpen, - FaCreditCard, - FaRegComment, - FaPhone, FaChevronLeft, FaChevronRight, } from 'react-icons/fa' @@ -21,178 +9,876 @@ import { useLocalization } from '@/utils/hooks/useLocalization' import { ROUTES_ENUM } from '@/routes/route.constant' import { Helmet } from 'react-helmet' import { APP_NAME } from '@/constants/app.constant' +import { useStoreActions, useStoreState } from '@/store' +import Loading from '@/components/shared/Loading' +import navigationIcon from '@/proxy/menus/navigation-icon.config' +import DesignerDrawer from './designer/DesignerDrawer' +import SelectableBlock from './designer/SelectableBlock' +import { DesignerSelection } from './designer/types' +import { useDesignerState } from './designer/useDesignerState' +import { getHome, HomeDto, saveHomePage } from '@/services/home.service' + +interface HomeSlideServiceContent { + icon: string + title: string + titleKey: string + description: string + descriptionKey: string +} + +interface HomeSlideContent { + title: string + titleKey: string + subtitle: string + subtitleKey: string + services: HomeSlideServiceContent[] +} + +interface HomeFeatureContent { + icon: string + title: string + titleKey: string + description: string + descriptionKey: string +} + +interface HomeSolutionContent { + icon: string + colorClass: string + title: string + titleKey: string + description: string + descriptionKey: string +} + +interface HomeContent { + heroBackgroundImage: string + heroBackgroundImageKey: string + heroPrimaryCtaLabel: string + heroPrimaryCtaKey: string + heroSecondaryCtaLabel: string + heroSecondaryCtaKey: string + featuresTitle: string + featuresTitleKey: string + featuresSubtitle: string + featuresSubtitleKey: string + solutionsTitle: string + solutionsTitleKey: string + solutionsSubtitle: string + solutionsSubtitleKey: string + ctaTitle: string + ctaTitleKey: string + ctaSubtitle: string + ctaSubtitleKey: string + ctaButtonLabel: string + ctaButtonLabelKey: string + slides: HomeSlideContent[] + features: HomeFeatureContent[] + solutions: HomeSolutionContent[] +} + +const HOME_HERO_BACKGROUND_KEY = 'Public.home.hero.backgroundImage' +const HOME_HERO_DEFAULT_IMAGE = + 'https://images.pexels.com/photos/3183150/pexels-photo-3183150.jpeg?auto=compress&cs=tinysrgb&w=1920' + +const HOME_HERO_PRIMARY_CTA_KEY = 'Public.hero.cta.consultation' +const HOME_HERO_SECONDARY_CTA_KEY = 'Public.hero.cta.discover' +const HOME_FEATURES_TITLE_KEY = 'Public.features.title' +const HOME_FEATURES_SUBTITLE_KEY = 'Public.features.subtitle' +const HOME_SOLUTIONS_TITLE_KEY = 'Public.solutions.title' +const HOME_SOLUTIONS_SUBTITLE_KEY = 'Public.solutions.subtitle' +const HOME_CTA_TITLE_KEY = 'Public.common.getStarted' +const HOME_CTA_SUBTITLE_KEY = 'Public.common.contact' +const HOME_CTA_BUTTON_KEY = 'Public.common.learnMore' + +function isLikelyLocalizationKey(value?: string) { + return Boolean(value && /^[A-Za-z0-9_.-]+$/.test(value) && value.includes('.')) +} + +function resolveLocalizedValue( + translate: (key: string) => string, + keyOrValue: string | undefined, + fallback = '', +) { + if (!keyOrValue) { + return fallback + } + + if (!isLikelyLocalizationKey(keyOrValue)) { + return keyOrValue + } + + const translatedValue = translate('::' + keyOrValue) + return translatedValue === keyOrValue ? fallback || keyOrValue : translatedValue +} + +function defaultSlides() { + return [ + { + titleKey: 'Public.hero.slide1.title', + subtitleKey: 'Public.hero.slide1.subtitle', + services: [ + { + icon: 'FaCalendarAlt', + titleKey: 'Public.hero.slide1.service1.title', + descriptionKey: 'Public.hero.slide1.service1.desc', + }, + { + icon: 'FaUsers', + titleKey: 'Public.hero.slide1.service2.title', + descriptionKey: 'Public.hero.slide1.service2.desc', + }, + { + icon: 'FaShieldAlt', + titleKey: 'Public.hero.slide1.service3.title', + descriptionKey: 'Public.hero.slide1.service3.desc', + }, + ], + }, + { + titleKey: 'Public.hero.slide2.title', + subtitleKey: 'Public.hero.slide2.subtitle', + services: [ + { + icon: 'FaChartBar', + titleKey: 'Public.hero.slide2.service1.title', + descriptionKey: 'Public.hero.slide2.service1.desc', + }, + { + icon: 'FaCreditCard', + titleKey: 'Public.hero.slide2.service2.title', + descriptionKey: 'Public.hero.slide2.service2.desc', + }, + { + icon: 'FaDatabase', + titleKey: 'Public.hero.slide2.service3.title', + descriptionKey: 'Public.hero.slide2.service3.desc', + }, + ], + }, + { + titleKey: 'Public.hero.slide3.title', + subtitleKey: 'Public.hero.slide3.subtitle', + services: [ + { + icon: 'FaDesktop', + titleKey: 'Public.hero.slide3.service1.title', + descriptionKey: 'Public.hero.slide3.service1.desc', + }, + { + icon: 'FaServer', + titleKey: 'Public.hero.slide3.service2.title', + descriptionKey: 'Public.hero.slide3.service2.desc', + }, + { + icon: 'FaMobileAlt', + titleKey: 'Public.hero.slide3.service3.title', + descriptionKey: 'Public.hero.slide3.service3.desc', + }, + ], + }, + ] +} + +function defaultFeatures() { + return [ + { icon: 'FaUsers', titleKey: 'Public.features.reliable', descriptionKey: 'Public.features.reliable.desc' }, + { + icon: 'FaCalendarAlt', + titleKey: 'App.Coordinator.Classroom.Planning', + descriptionKey: 'Public.features.rapid.desc', + }, + { icon: 'FaBookOpen', titleKey: 'Public.features.expert', descriptionKey: 'Public.features.expert.desc' }, + { + icon: 'FaCreditCard', + titleKey: 'Public.features.muhasebe', + descriptionKey: 'Public.features.muhasebe.desc', + }, + { + icon: 'FaRegComment', + titleKey: 'Public.features.iletisim', + descriptionKey: 'Public.features.iletisim.desc', + }, + { icon: 'FaPhone', titleKey: 'Public.features.mobil', descriptionKey: 'Public.features.mobil.desc' }, + { icon: 'FaChartBar', titleKey: 'Public.features.scalable', descriptionKey: 'Public.features.scalable.desc' }, + { + icon: 'FaShieldAlt', + titleKey: 'Public.features.guvenlik', + descriptionKey: 'Public.features.guvenlik.desc', + }, + ] +} + +function defaultSolutions() { + return [ + { + icon: 'FaDesktop', + colorClass: 'bg-blue-600', + titleKey: 'Public.services.web.title', + descriptionKey: 'Public.solutions.web.desc', + }, + { + icon: 'FaMobileAlt', + colorClass: 'bg-purple-600', + titleKey: 'Public.services.mobile.title', + descriptionKey: 'Public.solutions.mobile.desc', + }, + { + icon: 'FaServer', + colorClass: 'bg-green-600', + titleKey: 'Public.solutions.custom.title', + descriptionKey: 'Public.solutions.custom.desc', + }, + { + icon: 'FaDatabase', + colorClass: 'bg-red-600', + titleKey: 'Public.solutions.database.title', + descriptionKey: 'Public.solutions.database.desc', + }, + ] +} + +function buildHomeContent(home: HomeDto | undefined, translate: (key: string) => string): HomeContent { + const slideItems = home?.slidesDto?.length ? home.slidesDto : defaultSlides() + const featureItems = home?.featuresDto?.length ? home.featuresDto : defaultFeatures() + const solutionItems = home?.solutionsDto?.length ? home.solutionsDto : defaultSolutions() + + return { + heroBackgroundImage: resolveLocalizedValue( + translate, + home?.heroBackgroundImageKey || HOME_HERO_BACKGROUND_KEY, + HOME_HERO_DEFAULT_IMAGE, + ), + heroBackgroundImageKey: home?.heroBackgroundImageKey || HOME_HERO_BACKGROUND_KEY, + heroPrimaryCtaLabel: resolveLocalizedValue( + translate, + home?.heroPrimaryCtaKey || HOME_HERO_PRIMARY_CTA_KEY, + ), + heroPrimaryCtaKey: home?.heroPrimaryCtaKey || HOME_HERO_PRIMARY_CTA_KEY, + heroSecondaryCtaLabel: resolveLocalizedValue( + translate, + home?.heroSecondaryCtaKey || HOME_HERO_SECONDARY_CTA_KEY, + ), + heroSecondaryCtaKey: home?.heroSecondaryCtaKey || HOME_HERO_SECONDARY_CTA_KEY, + featuresTitle: resolveLocalizedValue(translate, home?.featuresTitleKey || HOME_FEATURES_TITLE_KEY), + featuresTitleKey: home?.featuresTitleKey || HOME_FEATURES_TITLE_KEY, + featuresSubtitle: resolveLocalizedValue( + translate, + home?.featuresSubtitleKey || HOME_FEATURES_SUBTITLE_KEY, + ), + featuresSubtitleKey: home?.featuresSubtitleKey || HOME_FEATURES_SUBTITLE_KEY, + solutionsTitle: resolveLocalizedValue(translate, home?.solutionsTitleKey || HOME_SOLUTIONS_TITLE_KEY), + solutionsTitleKey: home?.solutionsTitleKey || HOME_SOLUTIONS_TITLE_KEY, + solutionsSubtitle: resolveLocalizedValue( + translate, + home?.solutionsSubtitleKey || HOME_SOLUTIONS_SUBTITLE_KEY, + ), + solutionsSubtitleKey: home?.solutionsSubtitleKey || HOME_SOLUTIONS_SUBTITLE_KEY, + ctaTitle: resolveLocalizedValue(translate, home?.ctaTitleKey || HOME_CTA_TITLE_KEY), + ctaTitleKey: home?.ctaTitleKey || HOME_CTA_TITLE_KEY, + ctaSubtitle: resolveLocalizedValue(translate, home?.ctaSubtitleKey || HOME_CTA_SUBTITLE_KEY), + ctaSubtitleKey: home?.ctaSubtitleKey || HOME_CTA_SUBTITLE_KEY, + ctaButtonLabel: resolveLocalizedValue(translate, home?.ctaButtonLabelKey || HOME_CTA_BUTTON_KEY), + ctaButtonLabelKey: home?.ctaButtonLabelKey || HOME_CTA_BUTTON_KEY, + slides: slideItems.map((slide, slideIndex) => ({ + title: resolveLocalizedValue(translate, slide.titleKey, slide.titleKey), + titleKey: slide.titleKey || `Public.home.dynamic.slide.${slideIndex + 1}.title`, + subtitle: resolveLocalizedValue(translate, slide.subtitleKey, slide.subtitleKey), + subtitleKey: slide.subtitleKey || `Public.home.dynamic.slide.${slideIndex + 1}.subtitle`, + services: (slide.services || []).map((service, serviceIndex) => ({ + icon: service.icon || 'FaCircle', + title: resolveLocalizedValue(translate, service.titleKey, service.titleKey), + titleKey: + service.titleKey || + `Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.title`, + description: resolveLocalizedValue(translate, service.descriptionKey, service.descriptionKey), + descriptionKey: + service.descriptionKey || + `Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.description`, + })), + })), + features: featureItems.map((feature, index) => ({ + icon: feature.icon || 'FaCircle', + title: resolveLocalizedValue(translate, feature.titleKey, feature.titleKey), + titleKey: feature.titleKey || `Public.home.dynamic.feature.${index + 1}.title`, + description: resolveLocalizedValue(translate, feature.descriptionKey, feature.descriptionKey), + descriptionKey: feature.descriptionKey || `Public.home.dynamic.feature.${index + 1}.description`, + })), + solutions: solutionItems.map((solution, index) => ({ + icon: solution.icon || 'FaCircle', + colorClass: solution.colorClass || 'bg-blue-600', + title: resolveLocalizedValue(translate, solution.titleKey, solution.titleKey), + titleKey: solution.titleKey || `Public.home.dynamic.solution.${index + 1}.title`, + description: resolveLocalizedValue(translate, solution.descriptionKey, solution.descriptionKey), + descriptionKey: + solution.descriptionKey || `Public.home.dynamic.solution.${index + 1}.description`, + })), + } +} const Home: React.FC = () => { const { translate } = useLocalization() + const { setLang } = useStoreActions((actions) => actions.locale) + const { getConfig } = useStoreActions((actions) => actions.abpConfig) + const configCultureName = useStoreState( + (state) => state.abpConfig.config?.localization.currentCulture.cultureName, + ) + const localeCurrentLang = useStoreState((state) => state.locale?.currentLang) + const currentLanguage = configCultureName || localeCurrentLang || 'tr' + const abpLanguages = useStoreState((state) => state.abpConfig.config?.localization.languages) || [] + const languageOptions = abpLanguages + .filter((language) => Boolean(language.cultureName)) + .map((language) => { + const cultureName = language.cultureName || 'tr' + return { + key: cultureName.toLowerCase().split('-')[0], + cultureName, + displayName: language.displayName || cultureName, + } + }) + const languagesFromConfig = languageOptions.map((language) => language.key) + const editorLanguages = Array.from( + new Set((languagesFromConfig.length > 0 ? languagesFromConfig : [currentLanguage]).filter(Boolean)), + ) + + const [home, setHome] = useState() + const [loading, setLoading] = useState(true) + const [isSaving, setIsSaving] = useState(false) + const [isPanelVisible, setIsPanelVisible] = useState(true) const [currentSlide, setCurrentSlide] = useState(0) - const slides = [ - { - title: translate('::Public.hero.slide1.title'), - subtitle: translate('::Public.hero.slide1.subtitle'), - services: [ - { - icon: , - title: translate('::Public.hero.slide1.service1.title'), - desc: translate('::Public.hero.slide1.service1.desc'), - }, - { - icon: , - title: translate('::Public.hero.slide1.service2.title'), - desc: translate('::Public.hero.slide1.service2.desc'), - }, - { - icon: , - title: translate('::Public.hero.slide1.service3.title'), - desc: translate('::Public.hero.slide1.service3.desc'), - }, - ], - }, - { - title: translate('::Public.hero.slide2.title'), - subtitle: translate('::Public.hero.slide2.subtitle'), - services: [ - { - icon: , - title: translate('::Public.hero.slide2.service1.title'), - desc: translate('::Public.hero.slide2.service1.desc'), - }, - { - icon: , - title: translate('::Public.hero.slide2.service2.title'), - desc: translate('::Public.hero.slide2.service2.desc'), - }, - { - icon: , - title: translate('::Public.hero.slide2.service3.title'), - desc: translate('::Public.hero.slide2.service3.desc'), - }, - ], - }, - { - title: translate('::Public.hero.slide3.title'), - subtitle: translate('::Public.hero.slide3.subtitle'), - services: [ - { - icon: , - title: translate('::Public.hero.slide3.service1.title'), - desc: translate('::Public.hero.slide3.service1.desc'), - }, - { - icon: , - title: translate('::Public.hero.slide3.service2.title'), - desc: translate('::Public.hero.slide3.service2.desc'), - }, - { - icon: , - title: translate('::Public.hero.slide3.service3.title'), - desc: translate('::Public.hero.slide3.service3.desc'), - }, - ], - }, - ] + const initialContent = !loading ? buildHomeContent(home, translate) : null + const { + content, + isDesignMode, + selectedBlockId, + selectedLanguage, + supportedLanguages, + setContent, + setSelectedBlockId, + resetContent, + } = useDesignerState('home', initialContent, { + currentLanguage, + supportedLanguages: editorLanguages, + }) useEffect(() => { - const timer = setInterval(() => { - setCurrentSlide((prev) => (prev + 1) % slides.length) - }, 10000) - return () => clearInterval(timer) + setLoading(true) + const fetchHome = async () => { + try { + const result = await getHome() + setHome(result.data) + } catch (error) { + console.error('Home alinirken hata olustu:', error) + } finally { + setLoading(false) + } + } + + fetchHome() }, []) + useEffect(() => { + if (!content?.slides?.length) { + return + } + + if (currentSlide >= content.slides.length) { + setCurrentSlide(0) + } + }, [content?.slides.length, currentSlide]) + + useEffect(() => { + if (!content?.slides?.length) { + return + } + + const timer = setInterval(() => { + setCurrentSlide((prev) => (prev + 1) % content.slides.length) + }, 10000) + + return () => clearInterval(timer) + }, [content?.slides.length]) + const nextSlide = () => { - setCurrentSlide((prev) => (prev + 1) % slides.length) + const size = content?.slides.length || 1 + setCurrentSlide((prev) => (prev + 1) % size) } const prevSlide = () => { - setCurrentSlide((prev) => (prev - 1 + slides.length) % slides.length) + const size = content?.slides.length || 1 + setCurrentSlide((prev) => (prev - 1 + size) % size) } - const features = [ - { - icon: , - title: translate('::Public.features.reliable'), - description: translate('::Public.features.reliable.desc'), - }, - { - icon: , - title: translate('::App.Coordinator.Classroom.Planning'), - description: translate('::Public.features.rapid.desc'), - }, - { - icon: , - title: translate('::Public.features.expert'), - description: translate('::Public.features.expert.desc'), - }, - { - icon: , - title: translate('::Public.features.muhasebe'), - description: translate('::Public.features.muhasebe.desc'), - }, - { - icon: , - title: translate('::Public.features.iletisim'), - description: translate('::Public.features.iletisim.desc'), - }, - { - icon: , - title: translate('::Public.features.mobil'), - description: translate('::Public.features.mobil.desc'), - }, - { - icon: , - title: translate('::Public.features.scalable'), - description: translate('::Public.features.scalable.desc'), - }, - { - icon: , - title: translate('::Public.features.guvenlik'), - description: translate('::Public.features.guvenlik.desc'), - }, - ] + const updateContent = (updater: (current: HomeContent) => HomeContent) => { + setContent((current) => { + if (!current) { + return current + } - const solutions = [ - { - icon: , - title: translate('::Public.services.web.title'), - description: translate('::Public.solutions.web.desc'), - color: 'bg-blue-600', - }, - { - icon: , - title: translate('::Public.services.mobile.title'), - description: translate('::Public.solutions.mobile.desc'), - color: 'bg-purple-600', - }, - { - icon: , - title: translate('::Public.solutions.custom.title'), - description: translate('::Public.solutions.custom.desc'), - color: 'bg-green-600', - }, - { - icon: , - title: translate('::Public.solutions.database.title'), - description: translate('::Public.solutions.database.desc'), - color: 'bg-red-600', - }, - ] + return updater(current) + }) + } + + const handleFieldChange = (fieldKey: string, value: string | string[]) => { + const nextValue = value as string + + updateContent((current) => { + if ( + fieldKey === 'heroBackgroundImage' || + fieldKey === 'heroPrimaryCtaLabel' || + fieldKey === 'heroSecondaryCtaLabel' || + fieldKey === 'featuresTitle' || + fieldKey === 'featuresSubtitle' || + fieldKey === 'solutionsTitle' || + fieldKey === 'solutionsSubtitle' || + fieldKey === 'ctaTitle' || + fieldKey === 'ctaSubtitle' || + fieldKey === 'ctaButtonLabel' + ) { + return { + ...current, + [fieldKey]: nextValue, + } + } + + if (selectedBlockId?.startsWith('slide-')) { + const index = Number(selectedBlockId.replace('slide-', '')) + if (Number.isNaN(index)) { + return current + } + + const slides = [...current.slides] + const target = { ...slides[index] } + + if (fieldKey === 'title' || fieldKey === 'subtitle') { + target[fieldKey] = nextValue + } else if (fieldKey.startsWith('service-')) { + const parts = fieldKey.split('-') + const serviceIndex = Number(parts[1]) + const serviceField = parts[2] as 'icon' | 'title' | 'description' + const services = [...target.services] + const service = { ...services[serviceIndex] } + service[serviceField] = nextValue + services[serviceIndex] = service + target.services = services + } + + slides[index] = target + return { + ...current, + slides, + } + } + + if (selectedBlockId?.startsWith('feature-')) { + const index = Number(selectedBlockId.replace('feature-', '')) + if (Number.isNaN(index)) { + return current + } + + const features = [...current.features] + const feature = { ...features[index] } + const key = fieldKey as 'icon' | 'title' | 'description' + feature[key] = nextValue + features[index] = feature + + return { + ...current, + features, + } + } + + if (selectedBlockId?.startsWith('solution-')) { + const index = Number(selectedBlockId.replace('solution-', '')) + if (Number.isNaN(index)) { + return current + } + + const solutions = [...current.solutions] + const solution = { ...solutions[index] } + const key = fieldKey as 'icon' | 'title' | 'description' | 'colorClass' + solution[key] = nextValue + solutions[index] = solution + + return { + ...current, + solutions, + } + } + + return current + }) + } + + const selectedSelection: DesignerSelection | null = React.useMemo(() => { + if (!content || !selectedBlockId) { + return null + } + + if (selectedBlockId === 'hero') { + return { + id: 'hero', + title: 'Public.home.hero.*', + description: 'Hero arka planini ve CTA metinlerini duzenleyin.', + fields: [ + { + key: 'heroBackgroundImage', + label: content.heroBackgroundImageKey, + type: 'image', + value: content.heroBackgroundImage, + }, + { + key: 'heroPrimaryCtaLabel', + label: content.heroPrimaryCtaKey, + type: 'text', + value: content.heroPrimaryCtaLabel, + }, + { + key: 'heroSecondaryCtaLabel', + label: content.heroSecondaryCtaKey, + type: 'text', + value: content.heroSecondaryCtaLabel, + }, + ], + } + } + + if (selectedBlockId.startsWith('slide-')) { + const index = Number(selectedBlockId.replace('slide-', '')) + const slide = content.slides[index] + + if (!slide) { + return null + } + + return { + id: selectedBlockId, + title: slide.titleKey, + description: 'Slayt baslik, alt baslik ve servis kartlarini duzenleyin.', + fields: [ + { + key: 'title', + label: slide.titleKey, + type: 'text', + value: slide.title, + }, + { + key: 'subtitle', + label: slide.subtitleKey, + type: 'textarea', + value: slide.subtitle, + }, + ...slide.services.flatMap((service, serviceIndex) => [ + { + key: `service-${serviceIndex}-icon`, + label: `Slide${index + 1}.Service${serviceIndex + 1}.Icon`, + type: 'icon' as const, + value: service.icon, + placeholder: 'Ornek: FaUsers', + }, + { + key: `service-${serviceIndex}-title`, + label: service.titleKey, + type: 'text' as const, + value: service.title, + }, + { + key: `service-${serviceIndex}-description`, + label: service.descriptionKey, + type: 'textarea' as const, + value: service.description, + }, + ]), + ], + } + } + + if (selectedBlockId === 'features-heading') { + return { + id: selectedBlockId, + title: content.featuresTitleKey, + description: 'Features bolum basliklarini duzenleyin.', + fields: [ + { + key: 'featuresTitle', + label: content.featuresTitleKey, + type: 'text', + value: content.featuresTitle, + }, + { + key: 'featuresSubtitle', + label: content.featuresSubtitleKey, + type: 'textarea', + value: content.featuresSubtitle, + }, + ], + } + } + + if (selectedBlockId.startsWith('feature-')) { + const index = Number(selectedBlockId.replace('feature-', '')) + const item = content.features[index] + + if (!item) { + return null + } + + return { + id: selectedBlockId, + title: item.titleKey, + description: 'Feature ikon ve metinlerini duzenleyin.', + fields: [ + { + key: 'icon', + label: `Feature${index + 1}.Icon`, + type: 'icon', + value: item.icon, + placeholder: 'Ornek: FaChartBar', + }, + { + key: 'title', + label: item.titleKey, + type: 'text', + value: item.title, + }, + { + key: 'description', + label: item.descriptionKey, + type: 'textarea', + value: item.description, + }, + ], + } + } + + if (selectedBlockId === 'solutions-heading') { + return { + id: selectedBlockId, + title: content.solutionsTitleKey, + description: 'Solutions bolum basliklarini duzenleyin.', + fields: [ + { + key: 'solutionsTitle', + label: content.solutionsTitleKey, + type: 'text', + value: content.solutionsTitle, + }, + { + key: 'solutionsSubtitle', + label: content.solutionsSubtitleKey, + type: 'textarea', + value: content.solutionsSubtitle, + }, + ], + } + } + + if (selectedBlockId.startsWith('solution-')) { + const index = Number(selectedBlockId.replace('solution-', '')) + const item = content.solutions[index] + + if (!item) { + return null + } + + return { + id: selectedBlockId, + title: item.titleKey, + description: 'Solution kartini tamamen duzenleyin.', + fields: [ + { + key: 'colorClass', + label: `Solution${index + 1}.ColorClass`, + type: 'text', + value: item.colorClass, + }, + { + key: 'icon', + label: `Solution${index + 1}.Icon`, + type: 'icon', + value: item.icon, + placeholder: 'Ornek: FaDesktop', + }, + { + key: 'title', + label: item.titleKey, + type: 'text', + value: item.title, + }, + { + key: 'description', + label: item.descriptionKey, + type: 'textarea', + value: item.description, + }, + ], + } + } + + if (selectedBlockId === 'cta') { + return { + id: selectedBlockId, + title: content.ctaTitleKey, + description: 'Sayfa alti cta metinlerini duzenleyin.', + fields: [ + { + key: 'ctaTitle', + label: content.ctaTitleKey, + type: 'text', + value: content.ctaTitle, + }, + { + key: 'ctaSubtitle', + label: content.ctaSubtitleKey, + type: 'textarea', + value: content.ctaSubtitle, + }, + { + key: 'ctaButtonLabel', + label: content.ctaButtonLabelKey, + type: 'text', + value: content.ctaButtonLabel, + }, + ], + } + } + + return null + }, [content, selectedBlockId]) + + const handleSaveAndExit = async () => { + if (!content || isSaving) { + return + } + + setIsSaving(true) + + try { + await saveHomePage({ + cultureName: selectedLanguage, + heroBackgroundImageKey: content.heroBackgroundImageKey, + heroBackgroundImageValue: content.heroBackgroundImage, + heroPrimaryCtaKey: content.heroPrimaryCtaKey, + heroPrimaryCtaValue: content.heroPrimaryCtaLabel, + heroSecondaryCtaKey: content.heroSecondaryCtaKey, + heroSecondaryCtaValue: content.heroSecondaryCtaLabel, + featuresTitleKey: content.featuresTitleKey, + featuresTitleValue: content.featuresTitle, + featuresSubtitleKey: content.featuresSubtitleKey, + featuresSubtitleValue: content.featuresSubtitle, + solutionsTitleKey: content.solutionsTitleKey, + solutionsTitleValue: content.solutionsTitle, + solutionsSubtitleKey: content.solutionsSubtitleKey, + solutionsSubtitleValue: content.solutionsSubtitle, + ctaTitleKey: content.ctaTitleKey, + ctaTitleValue: content.ctaTitle, + ctaSubtitleKey: content.ctaSubtitleKey, + ctaSubtitleValue: content.ctaSubtitle, + ctaButtonLabelKey: content.ctaButtonLabelKey, + ctaButtonLabelValue: content.ctaButtonLabel, + slides: content.slides.map((slide, slideIndex) => ({ + titleKey: slide.titleKey || `Public.home.dynamic.slide.${slideIndex + 1}.title`, + titleValue: slide.title, + subtitleKey: slide.subtitleKey || `Public.home.dynamic.slide.${slideIndex + 1}.subtitle`, + subtitleValue: slide.subtitle, + services: slide.services.map((service, serviceIndex) => ({ + icon: service.icon, + titleKey: + service.titleKey || + `Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.title`, + titleValue: service.title, + descriptionKey: + service.descriptionKey || + `Public.home.dynamic.slide.${slideIndex + 1}.service.${serviceIndex + 1}.description`, + descriptionValue: service.description, + })), + })), + features: content.features.map((feature, index) => ({ + icon: feature.icon, + titleKey: feature.titleKey || `Public.home.dynamic.feature.${index + 1}.title`, + titleValue: feature.title, + descriptionKey: feature.descriptionKey || `Public.home.dynamic.feature.${index + 1}.description`, + descriptionValue: feature.description, + })), + solutions: content.solutions.map((solution, index) => ({ + icon: solution.icon, + colorClass: solution.colorClass, + titleKey: solution.titleKey || `Public.home.dynamic.solution.${index + 1}.title`, + titleValue: solution.title, + descriptionKey: + solution.descriptionKey || `Public.home.dynamic.solution.${index + 1}.description`, + descriptionValue: solution.description, + })), + }) + + await getConfig(false) + setSelectedBlockId(null) + } catch (error) { + console.error('Home tasarimi kaydedilemedi:', error) + } finally { + setIsSaving(false) + } + } + + const handleLanguageChange = (language: string) => { + setLang(language) + } + + const handleSelectBlock = (blockId: string) => { + setSelectedBlockId(blockId) + if (!isPanelVisible) { + setIsPanelVisible(true) + } + } + + if (loading) { + return ( +
+
+ +
+
+ ) + } return ( -
+
+ {isDesignMode && ( +
+ Home designer aktif +
+ )} + + {isDesignMode && !isPanelVisible && ( + + )} + {/* Hero Carousel */} +
{ {/* Carousel Content */}
- {slides.map((slide, index) => ( + {(content?.slides || []).map((slide, index) => (
{ : 'opacity-0 translate-x-full' }`} > + { + setCurrentSlide(index) + handleSelectBlock(id) + }} + className="h-full" + >

@@ -225,14 +921,14 @@ const Home: React.FC = () => { 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" > - {translate('::Public.hero.cta.consultation')}{' '} + {content?.heroPrimaryCtaLabel}{' '} - {translate('::Public.hero.cta.discover')} + {content?.heroSecondaryCtaLabel}

@@ -242,14 +938,20 @@ const Home: React.FC = () => { key={i} className="bg-white/5 backdrop-blur-sm rounded-2xl p-8 text-center hover:scale-105 hover:bg-white/10 transition-all" > - {service.icon} + {(() => { + const IconComponent = navigationIcon[service.icon || ''] + return IconComponent ? ( + + ) : null + })()}

{service.title}

-

{service.desc}

+

{service.description}

))}
+
))}
@@ -272,7 +974,7 @@ const Home: React.FC = () => { {/* Slide Indicators */}
- {slides.map((_, index) => ( + {(content?.slides || []).map((_, index) => (
+ + {isDesignMode && ( +
+ {(content?.slides || []).map((_, index) => ( + + ))} +
+ )}
+ {/* Features */}
+

- {translate('::Public.features.title')} + {content?.featuresTitle}

- {translate('::Public.features.subtitle')} + {content?.featuresSubtitle}

+
- {features.map((feature, i) => ( -
( + -
{feature.icon}
+
+
+ {(() => { + const IconComponent = navigationIcon[feature.icon || ''] + return IconComponent ? : null + })()} +

{feature.title}

{feature.description}

+
))}
@@ -315,47 +1055,88 @@ const Home: React.FC = () => { {/* Solutions */}
+

- {translate('::Public.solutions.title')} + {content?.solutionsTitle}

- {translate('::Public.solutions.subtitle')} + {content?.solutionsSubtitle}

+
- {solutions.map((s, i) => ( -
( + -
-
{s.icon}
+
+
+ {(() => { + const IconComponent = navigationIcon[s.icon || ''] + return IconComponent ? : null + })()} +

{s.title}

{s.description}

-
+
))}
{/* Call to Action */} +
-

- {translate('::Public.common.getStarted')} -

-

{translate('::Public.common.contact')}

+

{content?.ctaTitle}

+

{content?.ctaSubtitle}

- {translate('::Public.common.learnMore')} + {content?.ctaButtonLabel}
+
+ + 0 + ? languageOptions + : supportedLanguages.map((language) => ({ + key: language, + cultureName: language, + displayName: language.toUpperCase(), + })) + } + onClose={() => setIsPanelVisible(false)} + onSave={handleSaveAndExit} + onLanguageChange={handleLanguageChange} + onReset={resetContent} + onFieldChange={handleFieldChange} + />
) } diff --git a/ui/src/views/public/designer/SelectableBlock.tsx b/ui/src/views/public/designer/SelectableBlock.tsx index f733e73..76781b9 100644 --- a/ui/src/views/public/designer/SelectableBlock.tsx +++ b/ui/src/views/public/designer/SelectableBlock.tsx @@ -30,7 +30,10 @@ const SelectableBlock: React.FC = ({ : 'ring-1 ring-sky-200/80 hover:ring-sky-400', className, )} - onClick={() => onSelect(id)} + onClick={(e) => { + e.stopPropagation() + onSelect(id) + }} role="button" tabIndex={0} onKeyDown={(event) => {