About ve Contact sayfaları dinamik şekilde değiştirildi.

This commit is contained in:
Sedat ÖZTÜRK 2025-08-21 14:41:59 +03:00
parent 6fdc2c3231
commit 1240c8dfb4
22 changed files with 1177 additions and 605 deletions

View file

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Volo.Abp.Application.Dtos;
namespace Kurs.Platform.Abouts;
public class AboutDto : EntityDto<Guid>
{
public Guid? TenantId { get; set; }
[JsonIgnore]
public string StatsJson { get; set; }
public List<StatDto> StatsDto
{
get
{
if (!string.IsNullOrEmpty(StatsJson))
return JsonSerializer.Deserialize<List<StatDto>>(StatsJson);
return new List<StatDto>();
}
set { StatsJson = JsonSerializer.Serialize(value); }
}
[JsonIgnore]
public string DescriptionsJson { get; set; }
public List<string> DescriptionsDto
{
get
{
if (!string.IsNullOrEmpty(DescriptionsJson))
return JsonSerializer.Deserialize<List<string>>(DescriptionsJson);
return [];
}
set { DescriptionsJson = JsonSerializer.Serialize(value); }
}
[JsonIgnore]
public string SectionsJson { get; set; }
public List<SectionDto> SectionsDto
{
get
{
if (!string.IsNullOrEmpty(SectionsJson))
return JsonSerializer.Deserialize<List<SectionDto>>(SectionsJson);
return new List<SectionDto>();
}
set { SectionsJson = JsonSerializer.Serialize(value); }
}
public class StatDto
{
public string Icon { get; set; }
public string Value { get; set; } // number/string farkını normalize ettik
public string LabelKey { get; set; }
public bool? UseCounter { get; set; }
public int? CounterEnd { get; set; }
public string CounterSuffix { get; set; }
public int? CounterDuration { get; set; }
}
public class SectionDto
{
public string Key { get; set; }
public string DescKey { get; set; }
}
}

View file

@ -0,0 +1,81 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Volo.Abp.Application.Dtos;
namespace Kurs.Platform.Contacts;
public class ContactDto : EntityDto<Guid>
{
public string Address { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Location { get; set; }
public string TaxNumber { get; set; }
[JsonIgnore]
public string BankJson { get; set; }
public BankDto BankDto
{
get
{
if (!string.IsNullOrEmpty(BankJson))
return JsonSerializer.Deserialize<BankDto>(BankJson);
return new BankDto();
}
set { BankJson = JsonSerializer.Serialize(value); }
}
[JsonIgnore]
public string WorkHoursJson { get; set; }
public WorkHoursDto WorkHoursDto
{
get
{
if (!string.IsNullOrEmpty(WorkHoursJson))
return JsonSerializer.Deserialize<WorkHoursDto>(WorkHoursJson);
return new WorkHoursDto();
}
set { WorkHoursJson = JsonSerializer.Serialize(value); }
}
[JsonIgnore]
public string MapJson { get; set; }
public MapDto MapDto
{
get
{
if (!string.IsNullOrEmpty(MapJson))
return JsonSerializer.Deserialize<MapDto>(MapJson);
return new MapDto();
}
set { MapJson = JsonSerializer.Serialize(value); }
}
}
public class BankDto
{
public string AccountHolder { get; set; }
public string Branch { get; set; }
public string AccountNumber { get; set; }
public string Iban { get; set; }
}
public class WorkHoursDto
{
public string Weekday { get; set; }
public string Weekend { get; set; }
public string Whatsapp { get; set; }
}
public class MapDto
{
public string Title { get; set; }
public string Src { get; set; }
public string Width { get; set; }
public string Height { get; set; }
public string Style { get; set; }
public bool? AllowFullScreen { get; set; }
public string Loading { get; set; }
public string ReferrerPolicy { get; set; }
}

View file

@ -15,6 +15,8 @@ using System.Linq;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Kurs.Platform.Orders; using Kurs.Platform.Orders;
using System.Text.Json; using System.Text.Json;
using Kurs.Platform.Abouts;
using Kurs.Platform.Contacts;
namespace Kurs.Platform.Public; namespace Kurs.Platform.Public;
@ -30,6 +32,8 @@ public class PublicAppService : PlatformAppService
private readonly IRepository<PaymentMethod, string> _paymentMethodRepository; private readonly IRepository<PaymentMethod, string> _paymentMethodRepository;
private readonly IRepository<InstallmentOption> _installmentOptionRepository; private readonly IRepository<InstallmentOption> _installmentOptionRepository;
private readonly IRepository<Order, Guid> _orderRepository; private readonly IRepository<Order, Guid> _orderRepository;
private readonly IRepository<About, Guid> _aboutRepository;
private readonly IRepository<Contact, Guid> _contactRepository;
public PublicAppService( public PublicAppService(
IRepository<Service, Guid> serviceRepository, IRepository<Service, Guid> serviceRepository,
@ -41,7 +45,9 @@ public class PublicAppService : PlatformAppService
IRepository<Product, Guid> productRepository, IRepository<Product, Guid> productRepository,
IRepository<PaymentMethod, string> paymentMethodRepository, IRepository<PaymentMethod, string> paymentMethodRepository,
IRepository<InstallmentOption> installmentOptionRepository, IRepository<InstallmentOption> installmentOptionRepository,
IRepository<Order, Guid> orderRepository IRepository<Order, Guid> orderRepository,
IRepository<About, Guid> aboutRepository,
IRepository<Contact, Guid> contactRepository
) )
{ {
_serviceRepository = serviceRepository; _serviceRepository = serviceRepository;
@ -54,6 +60,8 @@ public class PublicAppService : PlatformAppService
_paymentMethodRepository = paymentMethodRepository; _paymentMethodRepository = paymentMethodRepository;
_installmentOptionRepository = installmentOptionRepository; _installmentOptionRepository = installmentOptionRepository;
_orderRepository = orderRepository; _orderRepository = orderRepository;
_aboutRepository = aboutRepository;
_contactRepository = contactRepository;
} }
public async Task<List<ServiceDto>> GetServicesListAsync() public async Task<List<ServiceDto>> GetServicesListAsync()
@ -268,4 +276,17 @@ public class PublicAppService : PlatformAppService
PaymentMethod = entity.PaymentMethod PaymentMethod = entity.PaymentMethod
}; };
} }
public async Task<AboutDto> GetAboutAsync()
{
var entity = await _aboutRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(About));
return ObjectMapper.Map<About, AboutDto>(entity);
}
public async Task<ContactDto> GetContactAsync()
{
var entity = await _contactRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Contact));
return ObjectMapper.Map<Contact, ContactDto>(entity);
}
} }

View file

@ -1,5 +1,7 @@
using AutoMapper; using AutoMapper;
using Kurs.Platform.Abouts;
using Kurs.Platform.Blog; using Kurs.Platform.Blog;
using Kurs.Platform.Contacts;
using Kurs.Platform.Demos; using Kurs.Platform.Demos;
using Kurs.Platform.Entities; using Kurs.Platform.Entities;
using Kurs.Platform.Orders; using Kurs.Platform.Orders;
@ -20,5 +22,7 @@ public class PublicAutoMapperProfile : Profile
CreateMap<PaymentMethod, PaymentMethodDto>(); CreateMap<PaymentMethod, PaymentMethodDto>();
CreateMap<InstallmentOption, InstallmentOptionDto>(); CreateMap<InstallmentOption, InstallmentOptionDto>();
CreateMap<Order, OrderDto>(); CreateMap<Order, OrderDto>();
CreateMap<About, AboutDto>();
CreateMap<Contact, ContactDto>();
} }
} }

View file

@ -66,6 +66,8 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency
private readonly IRepository<CustomComponent, Guid> _customComponentRepository; private readonly IRepository<CustomComponent, Guid> _customComponentRepository;
private readonly IRepository<ReportCategory, Guid> _reportCategoriesRepository; private readonly IRepository<ReportCategory, Guid> _reportCategoriesRepository;
private readonly IRepository<Service, Guid> _servicesRepository; private readonly IRepository<Service, Guid> _servicesRepository;
private readonly IRepository<About, Guid> _aboutRepository;
private readonly IRepository<Contact, Guid> _contactRepository;
public PlatformDataSeeder( public PlatformDataSeeder(
IRepository<Language, Guid> languages, IRepository<Language, Guid> languages,
@ -104,7 +106,9 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency
IRepository<InstallmentOption, Guid> InstallmentOptionRepository, IRepository<InstallmentOption, Guid> InstallmentOptionRepository,
IRepository<CustomComponent, Guid> CustomComponentRepository, IRepository<CustomComponent, Guid> CustomComponentRepository,
IRepository<ReportCategory, Guid> ReportCategoriesRepository, IRepository<ReportCategory, Guid> ReportCategoriesRepository,
IRepository<Service, Guid> ServicesRepository IRepository<Service, Guid> ServicesRepository,
IRepository<About, Guid> AboutRepository,
IRepository<Contact, Guid> ContactRepository
) )
{ {
_languages = languages; _languages = languages;
@ -144,6 +148,8 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency
_customComponentRepository = CustomComponentRepository; _customComponentRepository = CustomComponentRepository;
_reportCategoriesRepository = ReportCategoriesRepository; _reportCategoriesRepository = ReportCategoriesRepository;
_servicesRepository = ServicesRepository; _servicesRepository = ServicesRepository;
_aboutRepository = AboutRepository;
_contactRepository = ContactRepository;
} }
private static IConfigurationRoot BuildConfiguration() private static IConfigurationRoot BuildConfiguration()
@ -156,6 +162,243 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency
return builder.Build(); return builder.Build();
} }
public async Task SeedCountyGroupsAsync()
{
var dbCtx = await _countryRepository.GetDbContextAsync();
// DBde mevcut kodları set olarak al
var existingCodes = (await dbCtx.Set<CountryGroup>()
.Select(c => c.Name)
.ToListAsync())
.ToHashSet();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
using var fs = File.OpenRead(Path.Combine("Seeds", "CountryGroups.json"));
var buffer = new List<CountryGroup>(capacity: 1000);
var seenCodes = new HashSet<string>(); // JSON içindeki duplicateleri yakalamak için
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CountryGroupDto>(fs, options))
{
if (item == null) continue;
if (string.IsNullOrWhiteSpace(item.Name)) continue; // boş kodları atla
// hem DBde hem JSON içinde duplicate engelle
if (!seenCodes.Add(item.Name) || existingCodes.Contains(item.Name))
continue;
buffer.Add(new CountryGroup(
Guid.NewGuid(),
item.Name
));
if (buffer.Count >= 1000)
{
await BulkCountryGroupInsertAsync(buffer);
buffer.Clear();
}
}
if (buffer.Count > 0)
{
await BulkCountryGroupInsertAsync(buffer);
}
}
private async Task BulkCountryGroupInsertAsync(List<CountryGroup> entities)
{
var dbCtx = await _countryGroupRepository.GetDbContextAsync();
await dbCtx.BulkInsertAsync(entities, new BulkConfig
{
BatchSize = 1000
});
}
public async Task SeedCountriesAsync()
{
var dbCtx = await _countryRepository.GetDbContextAsync();
// DBde mevcut kodları set olarak al
var existingCodes = (await dbCtx.Set<Country>()
.Select(c => c.Code)
.ToListAsync())
.ToHashSet();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
using var fs = File.OpenRead(Path.Combine("Seeds", "Countries.json"));
var buffer = new List<Country>(capacity: 1000);
var seenCodes = new HashSet<string>(); // JSON içindeki duplicateleri yakalamak için
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CountryDto>(fs, options))
{
if (item == null) continue;
if (string.IsNullOrWhiteSpace(item.Code)) continue; // boş kodları atla
// hem DBde hem JSON içinde duplicate engelle
if (!seenCodes.Add(item.Code) || existingCodes.Contains(item.Code))
continue;
buffer.Add(new Country(
Guid.NewGuid(),
item.Code,
item.Name,
item.GroupName,
item.CurrencyCode,
item.PhoneCode,
item.TaxLabel
));
if (buffer.Count >= 1000)
{
await BulkCountryInsertAsync(buffer);
buffer.Clear();
}
}
if (buffer.Count > 0)
{
await BulkCountryInsertAsync(buffer);
}
}
private async Task BulkCountryInsertAsync(List<Country> entities)
{
var dbCtx = await _countryRepository.GetDbContextAsync();
await dbCtx.BulkInsertAsync(entities, new BulkConfig
{
BatchSize = 1000
});
}
public async Task SeedCitiesAsync()
{
var dbCtx = await _cityRepository.GetDbContextAsync();
// 1. Mevcut kayıtları çek (tek sorguda)
var existingCities = await dbCtx.Set<City>()
.Select(d => new { d.Code })
.ToListAsync();
var existingSet = existingCities
.Select(d => d.Code)
.ToHashSet();
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
// 2. JSONu stream et
using FileStream fs = File.OpenRead(Path.Combine("Seeds", "Cities.json"));
var buffer = new List<City>(capacity: 5000);
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CityDto>(fs, options))
{
if (item == null) continue;
var key = $"{item.CountryCode}.{item.Code}";
if (existingSet.Contains(key)) continue; // duplicate kontrolü
buffer.Add(new City(
Guid.NewGuid(),
item.CountryCode,
item.Name,
key,
item.PlateCode
));
if (buffer.Count >= 1000) // 3. Batch
{
await BulkCityInsertAsync(buffer);
buffer.Clear();
}
}
if (buffer.Count > 0)
{
await BulkCityInsertAsync(buffer);
}
}
private async Task BulkCityInsertAsync(List<City> entities)
{
var dbCtx = await _cityRepository.GetDbContextAsync();
await dbCtx.BulkInsertAsync(entities, new BulkConfig
{
PreserveInsertOrder = true,
SetOutputIdentity = true,
BatchSize = 1000
});
}
public async Task SeedDistrictsAsync()
{
var dbCtx = await _districtRepository.GetDbContextAsync();
// 1. Mevcut kayıtları çek (tek sorguda)
var existingDistricts = await dbCtx.Set<District>()
.Select(d => new { d.CountryCode, d.CityCode, d.Name, d.Township, d.Street, d.ZipCode })
.ToListAsync();
var existingSet = existingDistricts
.Select(d => $"{d.CountryCode}:{d.CityCode}:{d.Name}:{d.Township}:{d.Street}:{d.ZipCode}")
.ToHashSet();
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
// 2. JSONu stream et
using FileStream fs = File.OpenRead(Path.Combine("Seeds", "Districts.json"));
var buffer = new List<District>(capacity: 5000);
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<DistrictDto>(fs, options))
{
if (item == null) continue;
var key = $"{item.CountryCode}:{item.CountryCode}.{item.CityCode}:{item.Name}:{item.Township}:{item.Street}:{item.ZipCode}";
var cityCode = $"{item.CountryCode}.{item.CityCode}";
if (existingSet.Contains(key)) continue;
buffer.Add(new District(
Guid.NewGuid(),
item.CountryCode,
cityCode,
item.Name,
item.Township,
item.Street,
item.ZipCode
));
if (buffer.Count >= 5000) // 3. Batch
{
await BulkDistrictInsertAsync(buffer);
buffer.Clear();
}
}
if (buffer.Count > 0)
{
await BulkDistrictInsertAsync(buffer);
}
}
private async Task BulkDistrictInsertAsync(List<District> entities)
{
var dbCtx = await _districtRepository.GetDbContextAsync();
await dbCtx.BulkInsertAsync(entities, new BulkConfig
{
PreserveInsertOrder = true,
SetOutputIdentity = true,
BatchSize = 5000
});
}
public async Task SeedAsync(DataSeedContext context) public async Task SeedAsync(DataSeedContext context)
{ {
var settings = await _settings.GetListAsync(); var settings = await _settings.GetListAsync();
@ -172,13 +415,7 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency
.Build(); .Build();
var items = configuration.Get<SeederDto>(); var items = configuration.Get<SeederDto>();
await SeedCountyGroupsAsync(); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
await SeedCountriesAsync();
await SeedCitiesAsync();
await SeedDistrictsAsync();
foreach (var item in items.Charts) foreach (var item in items.Charts)
{ {
@ -758,240 +995,46 @@ public class PlatformDataSeeder : IDataSeedContributor, ITransientDependency
)); ));
} }
} }
}
public async Task SeedCountyGroupsAsync() foreach (var item in items.Abouts)
{
var dbCtx = await _countryRepository.GetDbContextAsync();
// DBde mevcut kodları set olarak al
var existingCodes = (await dbCtx.Set<CountryGroup>()
.Select(c => c.Name)
.ToListAsync())
.ToHashSet();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
using var fs = File.OpenRead(Path.Combine("Seeds", "CountryGroups.json"));
var buffer = new List<CountryGroup>(capacity: 1000);
var seenCodes = new HashSet<string>(); // JSON içindeki duplicateleri yakalamak için
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CountryGroupDto>(fs, options))
{ {
if (item == null) continue; var exists = await _aboutRepository.FirstOrDefaultAsync();
if (string.IsNullOrWhiteSpace(item.Name)) continue; // boş kodları atla if (exists == null)
// hem DBde hem JSON içinde duplicate engelle
if (!seenCodes.Add(item.Name) || existingCodes.Contains(item.Name))
continue;
buffer.Add(new CountryGroup(
Guid.NewGuid(),
item.Name
));
if (buffer.Count >= 1000)
{ {
await BulkCountryGroupInsertAsync(buffer); await _aboutRepository.InsertAsync(new About(
buffer.Clear(); Guid.NewGuid(),
JsonSerializer.Serialize(item.Stats),
JsonSerializer.Serialize(item.Descriptions),
JsonSerializer.Serialize(item.Sections)
));
} }
} }
if (buffer.Count > 0) foreach (var item in items.Contacts)
{ {
await BulkCountryGroupInsertAsync(buffer); var exists = await _contactRepository.FirstOrDefaultAsync();
} if (exists == null)
}
private async Task BulkCountryGroupInsertAsync(List<CountryGroup> entities)
{
var dbCtx = await _countryGroupRepository.GetDbContextAsync();
await dbCtx.BulkInsertAsync(entities, new BulkConfig
{
BatchSize = 1000
});
}
public async Task SeedCountriesAsync()
{
var dbCtx = await _countryRepository.GetDbContextAsync();
// DBde mevcut kodları set olarak al
var existingCodes = (await dbCtx.Set<Country>()
.Select(c => c.Code)
.ToListAsync())
.ToHashSet();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
using var fs = File.OpenRead(Path.Combine("Seeds", "Countries.json"));
var buffer = new List<Country>(capacity: 1000);
var seenCodes = new HashSet<string>(); // JSON içindeki duplicateleri yakalamak için
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CountryDto>(fs, options))
{
if (item == null) continue;
if (string.IsNullOrWhiteSpace(item.Code)) continue; // boş kodları atla
// hem DBde hem JSON içinde duplicate engelle
if (!seenCodes.Add(item.Code) || existingCodes.Contains(item.Code))
continue;
buffer.Add(new Country(
Guid.NewGuid(),
item.Code,
item.Name,
item.GroupName,
item.CurrencyCode,
item.PhoneCode,
item.TaxLabel
));
if (buffer.Count >= 1000)
{ {
await BulkCountryInsertAsync(buffer); await _contactRepository.InsertAsync(new Contact(
buffer.Clear(); Guid.NewGuid(),
item.Address,
item.Phone,
item.Email,
item.Location,
item.TaxNumber,
JsonSerializer.Serialize(item.Bank),
JsonSerializer.Serialize(item.WorkHour),
JsonSerializer.Serialize(item.Map)
));
} }
} }
if (buffer.Count > 0) await SeedCountyGroupsAsync();
{
await BulkCountryInsertAsync(buffer);
}
}
private async Task BulkCountryInsertAsync(List<Country> entities) await SeedCountriesAsync();
{
var dbCtx = await _countryRepository.GetDbContextAsync();
await dbCtx.BulkInsertAsync(entities, new BulkConfig
{
BatchSize = 1000
});
}
public async Task SeedCitiesAsync() await SeedCitiesAsync();
{
var dbCtx = await _cityRepository.GetDbContextAsync();
// 1. Mevcut kayıtları çek (tek sorguda) await SeedDistrictsAsync();
var existingCities = await dbCtx.Set<City>()
.Select(d => new { d.Code })
.ToListAsync();
var existingSet = existingCities
.Select(d => d.Code)
.ToHashSet();
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
// 2. JSONu stream et
using FileStream fs = File.OpenRead(Path.Combine("Seeds", "Cities.json"));
var buffer = new List<City>(capacity: 5000);
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CityDto>(fs, options))
{
if (item == null) continue;
var key = $"{item.Code}";
if (existingSet.Contains(key)) continue; // duplicate kontrolü
buffer.Add(new City(
Guid.NewGuid(),
item.CountryCode,
item.Name,
$"{item.CountryCode}.{item.Code}",
item.PlateCode
));
if (buffer.Count >= 1000) // 3. Batch
{
await BulkCityInsertAsync(buffer);
buffer.Clear();
}
}
if (buffer.Count > 0)
{
await BulkCityInsertAsync(buffer);
}
}
private async Task BulkCityInsertAsync(List<City> entities)
{
var dbCtx = await _cityRepository.GetDbContextAsync();
await dbCtx.BulkInsertAsync(entities, new BulkConfig
{
PreserveInsertOrder = true,
SetOutputIdentity = true,
BatchSize = 1000
});
}
public async Task SeedDistrictsAsync()
{
var dbCtx = await _districtRepository.GetDbContextAsync();
// 1. Mevcut kayıtları çek (tek sorguda)
var existingDistricts = await dbCtx.Set<District>()
.Select(d => new { d.CountryCode, d.CityCode, d.Name, d.Township, d.Street, d.ZipCode })
.ToListAsync();
var existingSet = existingDistricts
.Select(d => $"{d.CountryCode}:{d.CityCode}:{d.Name}:{d.Township}:{d.Street}:{d.ZipCode}")
.ToHashSet();
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
// 2. JSONu stream et
using FileStream fs = File.OpenRead(Path.Combine("Seeds", "Districts.json"));
var buffer = new List<District>(capacity: 5000);
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<DistrictDto>(fs, options))
{
if (item == null) continue;
var key = $"{item.CountryCode}:{item.CityCode}:{item.Name}:{item.Township}:{item.Street}:{item.ZipCode}";
if (existingSet.Contains(key)) continue; // duplicate kontrolü
buffer.Add(new District(
Guid.NewGuid(),
item.CountryCode,
$"{item.CountryCode}.{item.CityCode}",
item.Name,
item.Township,
item.Street,
item.ZipCode
));
if (buffer.Count >= 5000) // 3. Batch
{
await BulkDistrictInsertAsync(buffer);
buffer.Clear();
}
}
if (buffer.Count > 0)
{
await BulkDistrictInsertAsync(buffer);
}
}
private async Task BulkDistrictInsertAsync(List<District> entities)
{
var dbCtx = await _districtRepository.GetDbContextAsync();
await dbCtx.BulkInsertAsync(entities, new BulkConfig
{
PreserveInsertOrder = true,
SetOutputIdentity = true,
BatchSize = 5000
});
} }
} }

View file

@ -17360,5 +17360,88 @@
"Public.services.support.sms.features.api" "Public.services.support.sms.features.api"
] ]
} }
],
"Abouts": [
{
"stats": [
{
"icon": "FaUsers",
"value": 300,
"labelKey": "Public.about.stats.clients",
"useCounter": true,
"counterEnd": 300,
"counterSuffix": "+",
"counterDuration": 2500
},
{
"icon": "FaAward",
"value": 20,
"labelKey": "Public.about.stats.experience",
"useCounter": true,
"counterEnd": 20,
"counterSuffix": "+",
"counterDuration": 2000
},
{
"icon": "FaClock",
"value": "7/24",
"labelKey": "Public.about.stats.support",
"useCounter": false
},
{
"icon": "FaGlobe",
"value": 3,
"labelKey": "Public.about.stats.countries",
"useCounter": true,
"counterEnd": 3,
"counterDuration": 1500
}
],
"descriptions": [
"Public.about.description.part1",
"Public.about.description.motto",
"Public.about.description.part2",
"Public.about.description.closing"
],
"sections": [
{
"key": "Public.about.mission",
"descKey": "Public.about.mission.desc"
},
{
"key": "Public.about.vision",
"descKey": "Public.about.vision.desc"
}
]
}
],
"Contacts": [
{
"address": "Public.contact.address.full",
"phone": "+90 (544) 769 7 638",
"email": "destek@sozsoft.com",
"location": "Kozyatağı",
"taxNumber": "32374982750",
"bank": {
"accountHolder": "Özlem Öztürk",
"branch": "03663 / Enpara",
"accountNumber": "73941177",
"iban": "TR65 0011 1000 0000 0073 9411 77"
},
"workHour": {
"weekday": "Public.contact.workHours.weekday",
"weekend": "Public.contact.workHours.weekend",
"whatsapp": "Public.contact.workHours.whatsapp"
},
"map": {
"title": "Public.contact.location",
"src": "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3006.209566407676!2d28.757000999999992!3d41.10811400000001!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0x8a0bbbdfcfd3fd24!2zU8O2enNvZnQ!5e0!3m2!1str!2str!4v1450816303558",
"width": "100%",
"height": "700",
"allowFullScreen": true,
"loading": "lazy",
"referrerPolicy": "no-referrer-when-downgrade"
}
}
] ]
} }

View file

@ -2,9 +2,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using Kurs.Languages.Entities; using Kurs.Languages.Entities;
using Kurs.Platform.Charts.Dto; using Kurs.Platform.Charts.Dto;
using Kurs.Platform.Contacts;
using Kurs.Platform.Entities; using Kurs.Platform.Entities;
using Kurs.Platform.ListForms; using Kurs.Platform.ListForms;
using Kurs.Settings.Entities; using Kurs.Settings.Entities;
using static Kurs.Platform.Abouts.AboutDto;
namespace Kurs.Platform.Seeds; namespace Kurs.Platform.Seeds;
@ -42,6 +44,8 @@ public class SeederDto
public List<CustomComponentSeedDto> CustomComponents { get; set; } public List<CustomComponentSeedDto> CustomComponents { get; set; }
public List<ReportCategorySeedDto> ReportCategories { get; set; } public List<ReportCategorySeedDto> ReportCategories { get; set; }
public List<ServiceSeedDto> Services { get; set; } public List<ServiceSeedDto> Services { get; set; }
public List<AboutSeedDto> Abouts { get; set; }
public List<ContactSeedDto> Contacts { get; set; }
} }
public class ChartsSeedDto public class ChartsSeedDto
@ -301,3 +305,22 @@ public class ServiceSeedDto
public string Type { get; set; } public string Type { get; set; }
public string[] Features { get; set; } public string[] Features { get; set; }
} }
public class AboutSeedDto
{
public List<StatDto> Stats { get; set; }
public List<string> Descriptions { get; set; }
public List<SectionDto> Sections { get; set; }
}
public class ContactSeedDto
{
public string Address { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Location { get; set; }
public string TaxNumber { get; set; }
public BankDto Bank { get; set; }
public WorkHoursDto WorkHour { get; set; }
public MapDto Map { get; set; }
}

View file

@ -0,0 +1,28 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Kurs.Platform.Entities;
public class About : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public string StatsJson { get; set; }
public string DescriptionsJson { get; set; }
public string SectionsJson { get; set; }
protected About() { }
public About(
Guid id,
string statsJson,
string descriptionsJson,
string sectionsJson
) : base(id)
{
StatsJson = statsJson;
DescriptionsJson = descriptionsJson;
SectionsJson = sectionsJson;
}
}

View file

@ -0,0 +1,45 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Kurs.Platform.Entities;
public class Contact : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Location { get; set; }
public string TaxNumber { get; set; }
// JSON string kolonlar
public string BankJson { get; set; }
public string WorkHoursJson { get; set; }
public string MapJson { get; set; }
protected Contact() { }
public Contact(
Guid id,
string address,
string phone,
string email,
string location,
string taxNumber,
string bankJson,
string workHoursJson,
string mapJson
) : base(id)
{
Address = address;
Phone = phone;
Email = email;
Location = location;
TaxNumber = taxNumber;
BankJson = bankJson;
WorkHoursJson = workHoursJson;
MapJson = mapJson;
}
}

View file

@ -80,6 +80,9 @@ public class PlatformDbContext :
public DbSet<PaymentMethod> PaymentMethods { get; set; } public DbSet<PaymentMethod> PaymentMethods { get; set; }
public DbSet<InstallmentOption> InstallmentOptions { get; set; } public DbSet<InstallmentOption> InstallmentOptions { get; set; }
public DbSet<About> Abouts { get; set; }
public DbSet<Contact> Contacts { get; set; }
public DbSet<Order> Orders { get; set; } public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; } public DbSet<OrderItem> OrderItems { get; set; }
@ -832,5 +835,33 @@ public class PlatformDbContext :
) )
); );
}); });
builder.Entity<About>(b =>
{
b.ToTable(PlatformConsts.DbTablePrefix + nameof(About), PlatformConsts.DbSchema);
b.ConfigureByConvention();
// JSON alanlar
b.Property(x => x.StatsJson).HasColumnType("nvarchar(max)");
b.Property(x => x.DescriptionsJson).HasColumnType("nvarchar(max)");
b.Property(x => x.SectionsJson).HasColumnType("nvarchar(max)");
});
builder.Entity<Contact>(b =>
{
b.ToTable(PlatformConsts.DbTablePrefix + nameof(Contact), PlatformConsts.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Address).HasMaxLength(500);
b.Property(x => x.Phone).HasMaxLength(50);
b.Property(x => x.Email).HasMaxLength(150);
b.Property(x => x.Location).HasMaxLength(150);
b.Property(x => x.TaxNumber).HasMaxLength(50);
// JSON alanlar nvarchar(max) olarak
b.Property(x => x.BankJson).HasColumnType("nvarchar(max)");
b.Property(x => x.WorkHoursJson).HasColumnType("nvarchar(max)");
b.Property(x => x.MapJson).HasColumnType("nvarchar(max)");
});
} }
} }

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Kurs.Platform.Migrations namespace Kurs.Platform.Migrations
{ {
[DbContext(typeof(PlatformDbContext))] [DbContext(typeof(PlatformDbContext))]
[Migration("20250820190627_GenelDuzenleme")] [Migration("20250821081019_GenelDuzenleme")]
partial class GenelDuzenleme partial class GenelDuzenleme
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -650,6 +650,59 @@ namespace Kurs.Platform.Migrations
b.ToTable("PNotificationRule", (string)null); b.ToTable("PNotificationRule", (string)null);
}); });
modelBuilder.Entity("Kurs.Platform.Entities.About", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("DescriptionsJson")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("SectionsJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("StatsJson")
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.HasKey("Id");
b.ToTable("PAbout", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.AiBot", b => modelBuilder.Entity("Kurs.Platform.Entities.AiBot", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -1506,6 +1559,79 @@ namespace Kurs.Platform.Migrations
b.ToTable("PCity", (string)null); b.ToTable("PCity", (string)null);
}); });
modelBuilder.Entity("Kurs.Platform.Entities.Contact", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Address")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("BankJson")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("Email")
.HasMaxLength(150)
.HasColumnType("nvarchar(150)");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Location")
.HasMaxLength(150)
.HasColumnType("nvarchar(150)");
b.Property<string>("MapJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("Phone")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("TaxNumber")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("WorkHoursJson")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("PContact", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.Country", b => modelBuilder.Entity("Kurs.Platform.Entities.Country", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")

View file

@ -173,6 +173,55 @@ namespace Kurs.Platform.Migrations
table: "PCity", table: "PCity",
columns: new[] { "CountryCode", "Code" }); columns: new[] { "CountryCode", "Code" });
migrationBuilder.CreateTable(
name: "PAbout",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
StatsJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
DescriptionsJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
SectionsJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PAbout", x => x.Id);
});
migrationBuilder.CreateTable(
name: "PContact",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Address = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
Phone = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
Email = table.Column<string>(type: "nvarchar(150)", maxLength: 150, nullable: true),
Location = table.Column<string>(type: "nvarchar(150)", maxLength: 150, nullable: true),
TaxNumber = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
BankJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
WorkHoursJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
MapJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PContact", x => x.Id);
});
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "PDemo", name: "PDemo",
columns: table => new columns: table => new
@ -598,6 +647,12 @@ namespace Kurs.Platform.Migrations
name: "FK_PUom_PUomCategory_UomCategoryId", name: "FK_PUom_PUomCategory_UomCategoryId",
table: "PUom"); table: "PUom");
migrationBuilder.DropTable(
name: "PAbout");
migrationBuilder.DropTable(
name: "PContact");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "PDemo"); name: "PDemo");

View file

@ -647,6 +647,59 @@ namespace Kurs.Platform.Migrations
b.ToTable("PNotificationRule", (string)null); b.ToTable("PNotificationRule", (string)null);
}); });
modelBuilder.Entity("Kurs.Platform.Entities.About", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("DescriptionsJson")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("SectionsJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("StatsJson")
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.HasKey("Id");
b.ToTable("PAbout", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.AiBot", b => modelBuilder.Entity("Kurs.Platform.Entities.AiBot", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -1503,6 +1556,79 @@ namespace Kurs.Platform.Migrations
b.ToTable("PCity", (string)null); b.ToTable("PCity", (string)null);
}); });
modelBuilder.Entity("Kurs.Platform.Entities.Contact", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Address")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("BankJson")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("Email")
.HasMaxLength(150)
.HasColumnType("nvarchar(150)");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Location")
.HasMaxLength(150)
.HasColumnType("nvarchar(150)");
b.Property<string>("MapJson")
.HasColumnType("nvarchar(max)");
b.Property<string>("Phone")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("TaxNumber")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("WorkHoursJson")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("PContact", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.Country", b => modelBuilder.Entity("Kurs.Platform.Entities.Country", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")

View file

@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812" "revision": "3ca0b8505b4bec776b69afdba2768812"
}, { }, {
"url": "index.html", "url": "index.html",
"revision": "0.vt922hqbnl8" "revision": "0.2cgibr83seg"
}], {}); }], {});
workbox.cleanupOutdatedCaches(); workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

View file

@ -0,0 +1,22 @@
export interface AboutDto {
id: string
tenantId?: string
statsDto: StatDto[]
descriptionsDto: string[]
sectionsDto: SectionDto[]
}
export interface StatDto {
icon: string
value: string
labelKey: string
useCounter?: boolean
counterEnd?: number
counterSuffix?: string
counterDuration?: number
}
export interface SectionDto {
key: string
descKey: string
}

View file

@ -0,0 +1,34 @@
export interface ContactDto {
id: string
address: string
phone: string
email: string
location: string
taxNumber: string
bankDto: BankDto
workHoursDto: WorkHoursDto
mapDto: MapDto
}
export interface BankDto {
accountHolder: string
branch: string
accountNumber: string
iban: string
}
export interface WorkHoursDto {
weekday: string
weekend: string
whatsapp: string
}
export interface MapDto {
title: string
src: string
width: string
height: string
allowFullScreen?: boolean
loading: string
referrerPolicy: string
}

12
ui/src/services/about.ts Normal file
View file

@ -0,0 +1,12 @@
import { AboutDto } from '@/proxy/about/models'
import apiService from './api.service'
export function getAbout() {
return apiService.fetchData<AboutDto>(
{
method: 'GET',
url: '/api/app/public/about',
},
{ apiName: 'Default' },
)
}

View file

@ -0,0 +1,12 @@
import apiService from './api.service'
import { ContactDto } from '@/proxy/contact/models'
export function getContact() {
return apiService.fetchData<ContactDto>(
{
method: 'GET',
url: '/api/app/public/contact',
},
{ apiName: 'Default' },
)
}

View file

@ -1,62 +0,0 @@
import { useState, useEffect, useRef } from 'react'
interface UseCountUpOptions {
start?: number
end: number
duration?: number
suffix?: string
prefix?: string
}
export const useCountUp = ({
start = 0,
end,
duration = 2000,
suffix = '',
prefix = '',
}: UseCountUpOptions) => {
const [count, setCount] = useState(start)
const [isInView, setIsInView] = useState(false)
const elementRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !isInView) {
setIsInView(true)
}
},
{ threshold: 0.1 },
)
if (elementRef.current) {
observer.observe(elementRef.current)
}
return () => observer.disconnect()
}, [isInView])
useEffect(() => {
if (!isInView) return
let startTime: number
const animateCount = (timestamp: number) => {
if (!startTime) startTime = timestamp
const progress = Math.min((timestamp - startTime) / duration, 1)
const currentCount = Math.floor(progress * (end - start) + start)
setCount(currentCount)
if (progress < 1) {
requestAnimationFrame(animateCount)
}
}
requestAnimationFrame(animateCount)
}, [isInView, start, end, duration])
const displayValue = `${prefix}${count}${suffix}`
return { count, displayValue, elementRef }
}

View file

@ -1,108 +1,44 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import {
FaUsers,
FaAward,
FaClock,
FaGlobe
} from 'react-icons/fa';
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { useCountUp } from '@/utils/hooks/useCountUp'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { IconType } from 'react-icons' import navigationIcon from '@/configs/navigation-icon.config'
import { AboutDto } from '@/proxy/about/models'
interface StatItem { import { getAbout } from '@/services/about'
icon: IconType
value: string | number
labelKey: string
useCounter?: boolean
counterEnd?: number
counterSuffix?: string
counterDuration?: number
}
interface ContentSection {
key: string
descKey: string
}
interface AboutPageData {
stats: StatItem[]
descriptions: string[]
sections: ContentSection[]
}
const About: React.FC = () => { const About: React.FC = () => {
const { translate } = useLocalization() const { translate } = useLocalization()
const [loading, setLoading] = useState(true)
const [about, setAbout] = useState<AboutDto>()
// About page data configuration const iconColors = [
const aboutPageData: AboutPageData = { 'text-blue-600',
stats: [ 'text-red-600',
{ 'text-green-600',
icon: FaUsers, 'text-purple-600',
value: 300, 'text-yellow-600',
labelKey: '::Public.about.stats.clients', 'text-indigo-600',
useCounter: true, ]
counterEnd: 300,
counterSuffix: '+', function getRandomColor() {
counterDuration: 2500 return iconColors[Math.floor(Math.random() * iconColors.length)]
},
{
icon: FaAward,
value: 20,
labelKey: '::Public.about.stats.experience',
useCounter: true,
counterEnd: 20,
counterSuffix: '+',
counterDuration: 2000
},
{
icon: FaClock,
value: '7/24',
labelKey: '::Public.about.stats.support',
useCounter: false
},
{
icon: FaGlobe,
value: 3,
labelKey: '::Public.about.stats.countries',
useCounter: true,
counterEnd: 3,
counterDuration: 1500
}
],
descriptions: [
'::Public.about.description.part1',
'::Public.about.description.motto',
'::Public.about.description.part2',
'::Public.about.description.closing'
],
sections: [
{
key: '::Public.about.mission',
descKey: '::Public.about.mission.desc'
},
{
key: '::Public.about.vision',
descKey: '::Public.about.vision.desc'
}
]
} }
// Counter animations based on data useEffect(() => {
const clientsCount = useCountUp({ setLoading(true)
end: aboutPageData.stats[0].counterEnd!,
suffix: aboutPageData.stats[0].counterSuffix, const fetchServices = async () => {
duration: aboutPageData.stats[0].counterDuration! try {
}) const result = await getAbout()
const experienceCount = useCountUp({ setAbout(result.data)
end: aboutPageData.stats[1].counterEnd!, } catch (error) {
suffix: aboutPageData.stats[1].counterSuffix, console.error('About alınırken hata oluştu:', error)
duration: aboutPageData.stats[1].counterDuration! } finally {
}) setLoading(false)
const countriesCount = useCountUp({ }
end: aboutPageData.stats[3].counterEnd!, }
duration: aboutPageData.stats[3].counterDuration!
}) fetchServices()
}, [])
return ( return (
<> <>
@ -136,32 +72,19 @@ const About: React.FC = () => {
<div className="py-10 bg-white"> <div className="py-10 bg-white">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8"> <div className="grid grid-cols-1 md:grid-cols-4 gap-8">
{aboutPageData.stats.map((stat, index) => { {about?.statsDto.map((stat, index) => {
const IconComponent = stat.icon const IconComponent = navigationIcon[stat.icon || '']
let displayValue = stat.value let displayValue = stat.value
let elementRef = undefined let elementRef = undefined
// Handle counter values
if (stat.useCounter) {
if (index === 0) {
displayValue = clientsCount.displayValue
elementRef = clientsCount.elementRef
} else if (index === 1) {
displayValue = experienceCount.displayValue
elementRef = experienceCount.elementRef
} else if (index === 3) {
displayValue = countriesCount.displayValue
elementRef = countriesCount.elementRef
}
}
return ( return (
<div key={index} className="text-center" ref={elementRef}> <div key={index} className="text-center">
<IconComponent className="w-12 h-12 text-blue-600 mx-auto mb-4" /> {IconComponent && (
<div className="text-4xl font-bold text-gray-900 mb-2"> <IconComponent className={`w-12 h-12 mx-auto mb-4 ${getRandomColor()}`} />
{displayValue} )}
</div> <div className="text-4xl font-bold text-gray-900 mb-2">{displayValue}</div>
<div className="text-gray-600">{translate(stat.labelKey)}</div> <div className="text-gray-600">{translate('::' + stat.labelKey)}</div>
</div> </div>
) )
})} })}
@ -174,28 +97,24 @@ const About: React.FC = () => {
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="mb-6"> <div className="mb-6">
<div className="p-5 mx-auto mx-auto text-gray-800 text-lg leading-relaxed shadow-md bg-white border-l-4 border-blue-600"> <div className="p-5 mx-auto mx-auto text-gray-800 text-lg leading-relaxed shadow-md bg-white border-l-4 border-blue-600">
<p> <p>{translate('::' + about?.descriptionsDto[0])}</p>
{translate(aboutPageData.descriptions[0])}
</p>
<p className="text-center p-5 text-blue-800"> <p className="text-center p-5 text-blue-800">
{translate(aboutPageData.descriptions[1])} {translate('::' + about?.descriptionsDto[1])}
</p>
<p>
{translate(aboutPageData.descriptions[2])}
</p> </p>
<p>{translate('::' + about?.descriptionsDto[2])}</p>
<p className="text-center p-5 text-blue-800"> <p className="text-center p-5 text-blue-800">
{translate(aboutPageData.descriptions[3])} {translate('::' + about?.descriptionsDto[3])}
</p> </p>
</div> </div>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
{aboutPageData.sections.map((section, index) => ( {about?.sectionsDto.map((section, index) => (
<div key={index} className="bg-white p-8 rounded-xl shadow-lg"> <div key={index} className="bg-white p-8 rounded-xl shadow-lg">
<h3 className="text-2xl font-bold text-gray-900 mb-4"> <h3 className="text-2xl font-bold text-gray-900 mb-4">
{translate(section.key)} {translate('::' + section.key)}
</h3> </h3>
<p className="text-gray-700">{translate(section.descKey)}</p> <p className="text-gray-700">{translate('::' + section.descKey)}</p>
</div> </div>
))} ))}
</div> </div>

View file

@ -1,4 +1,4 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import { import {
FaMailBulk, FaMailBulk,
FaPhone, FaPhone,
@ -7,13 +7,35 @@ import {
FaBuilding, FaBuilding,
FaCalendarAlt, FaCalendarAlt,
FaCalendarCheck, FaCalendarCheck,
FaRegComment FaRegComment,
} from 'react-icons/fa'; } from 'react-icons/fa'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { ContactDto } from '@/proxy/contact/models'
import { getContact } from '@/services/contact'
const Contact: React.FC = () => { const Contact: React.FC = () => {
const { translate } = useLocalization() const { translate } = useLocalization()
const [loading, setLoading] = useState(true)
const [contact, setContact] = useState<ContactDto>()
useEffect(() => {
setLoading(true)
const fetchServices = async () => {
try {
const result = await getContact()
setContact(result.data)
} catch (error) {
console.error('About alınırken hata oluştu:', error)
} finally {
setLoading(false)
}
}
fetchServices()
}, [])
return ( return (
<div className="min-h-screen bg-gray-50"> <div className="min-h-screen bg-gray-50">
@ -42,51 +64,51 @@ const Contact: React.FC = () => {
</div> </div>
</div> </div>
{/* Stats Section */}
<div className="py-16"> <div className="py-16">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
{/* Contact Information */}
<div className="space-y-4"> <div className="space-y-4">
<div className="bg-white rounded-xl shadow-lg p-8"> <div className="bg-white rounded-xl shadow-lg p-8">
<h2 className="text-2xl font-bold text-gray-900 mb-6"> <h2 className="text-2xl font-bold text-gray-900 mb-6">
{translate('::Public.contact.info.title')} {translate('::Public.contact.info.title')}
</h2> </h2>
<div className="space-y-6"> <div className="space-y-4">
<div className="flex items-start space-x-4"> <div className="flex items-start space-x-2">
<FaMapPin className="w-6 h-6 text-blue-600 flex-shrink-0 mt-1" /> <FaMapPin className="w-5 h-5 text-blue-600 flex-shrink-0 mt-1" />
<div> <div>
<p className="text-gray-600">{translate('::Public.contact.address.full')}</p> <p className="text-gray-600">{translate('::' + contact?.address)}</p>
</div> </div>
</div> </div>
<div className="flex items-start space-x-4"> <div className="flex items-start space-x-2">
<FaPhone className="w-6 h-6 text-blue-600 flex-shrink-0" /> <FaPhone className="w-5 h-5 text-blue-600 flex-shrink-0" />
<div> <div>
<p className="text-gray-600">+90 (544) 769 7 638</p> <p className="text-gray-600">{contact?.phone}</p>
</div> </div>
</div> </div>
<div className="flex items-start space-x-4"> <div className="flex items-start space-x-2">
<FaMailBulk className="w-6 h-6 text-blue-600 flex-shrink-0" /> <FaMailBulk className="w-5 h-5 text-blue-600 flex-shrink-0" />
<div> <div>
<p className="text-gray-600"> <p className="text-gray-600">
<a <a
href="mailto:destek@sozsoft.com" href={`mailto:${contact?.email}`}
className="hover:underline text-blue-600" className="hover:underline text-blue-600"
> >
destek@sozsoft.com {contact?.email}
</a> </a>
</p> </p>
</div> </div>
</div> </div>
<div className="flex items-start space-x-4"> <div className="flex items-start space-x-2">
<FaBuilding className="w-6 h-6 text-blue-600 flex-shrink-0" /> <FaBuilding className="w-5 h-5 text-blue-600 flex-shrink-0" />
<div> <div>
<p className="text-gray-600">Kozyatağı</p> <p className="text-gray-600">{contact?.location}</p>
</div> </div>
</div> </div>
<div className="flex items-start space-x-4"> <div className="flex items-start space-x-2">
<FaFileAlt className="w-6 h-6 text-blue-600 flex-shrink-0" /> <FaFileAlt className="w-5 h-5 text-blue-600 flex-shrink-0" />
<div> <div>
<p className="text-gray-600">32374982750</p> <p className="text-gray-600">{contact?.taxNumber}</p>
</div> </div>
</div> </div>
</div> </div>
@ -96,21 +118,19 @@ const Contact: React.FC = () => {
<h2 className="text-2xl font-bold text-gray-900 mb-6"> <h2 className="text-2xl font-bold text-gray-900 mb-6">
{translate('::Public.contact.bank.title')} {translate('::Public.contact.bank.title')}
</h2> </h2>
<div className="space-y-4"> <div className="mb-2">
<img <img
src="/img/enpara.svg" src="/img/enpara.svg"
alt="Enpara Logo" alt="Enpara Logo"
className="w-24 object-contain mt-1 flex-shrink-0" className="w-24 object-contain mt-1 flex-shrink-0"
/> />
<div> <div>
<h3 className="font-semibold text-gray-900">Özlem Öztürk</h3> <h3 className="font-semibold text-gray-900 mb-1">
<p className="text-gray-600"> {contact?.bankDto.accountHolder}
03663 / Enpara </h3>
<br /> <p className="text-gray-600 mb-1 ml-1">{contact?.bankDto.branch}</p>
73941177 <p className="text-gray-600 mb-1 ml-1">{contact?.bankDto.accountNumber}</p>
<br /> <p className="text-gray-600 mb-1 ml-1">{contact?.bankDto.iban}</p>
TR65 0011 1000 0000 0073 9411 77
</p>
</div> </div>
</div> </div>
</div> </div>
@ -120,28 +140,24 @@ const Contact: React.FC = () => {
<h2 className="text-2xl font-bold text-gray-900 mb-6"> <h2 className="text-2xl font-bold text-gray-900 mb-6">
{translate('::Public.contact.workHours')} {translate('::Public.contact.workHours')}
</h2> </h2>
<div className="space-y-4"> <div className="space-y-2">
<div className="flex items-start space-x-4"> <div className="flex items-center space-x-2">
<div className="space-y-2"> <FaCalendarAlt className="w-5 h-5 text-blue-500" />
<div className="flex items-center space-x-2"> <p className="text-gray-600">
<FaCalendarAlt className="w-5 h-5 text-blue-500" /> {translate('::' + contact?.workHoursDto.weekday)}
<p className="text-gray-600"> </p>
{translate('::Public.contact.workHours.weekday')} </div>
</p> <div className="flex items-center space-x-2">
</div> <FaCalendarCheck className="w-5 h-5 text-blue-500" />
<div className="flex items-center space-x-2"> <p className="text-gray-600">
<FaCalendarCheck className="w-5 h-5 text-blue-500" /> {translate('::' + contact?.workHoursDto.weekend)}
<p className="text-gray-600"> </p>
{translate('::Public.contact.workHours.weekend')} </div>
</p> <div className="flex items-center space-x-2">
</div> <FaRegComment className="w-5 h-5 text-green-500" />
<div className="flex items-center space-x-2"> <p className="text-gray-600">
<FaRegComment className="w-5 h-5 text-green-500" /> {translate('::' + contact?.workHoursDto.whatsapp)}
<p className="text-gray-600"> </p>
{translate('::Public.contact.workHours.whatsapp')}
</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -150,17 +166,19 @@ const Contact: React.FC = () => {
{/* Map Section */} {/* Map Section */}
<div className="bg-white rounded-xl shadow-lg p-2"> <div className="bg-white rounded-xl shadow-lg p-2">
<h2 className="text-2xl font-bold text-gray-900 mb-8 text-center"> <h2 className="text-2xl font-bold text-gray-900 mb-8 text-center">
{translate('::Public.contact.location')} {translate('::' + contact?.mapDto.title)}
</h2> </h2>
<div className="aspect-w-16 aspect-h-9 bg-gray-200 rounded-xl overflow-hidden"> <div className="aspect-w-16 aspect-h-9 bg-gray-200 rounded-xl overflow-hidden">
<iframe <iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3006.209566407676!2d28.757000999999992!3d41.10811400000001!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0x8a0bbbdfcfd3fd24!2zU8O2enNvZnQ!5e0!3m2!1str!2str!4v1450816303558" src={contact?.mapDto.src}
width="100%" width={contact?.mapDto.width}
height="740"
style={{ border: 0 }} style={{ border: 0 }}
allowFullScreen height={contact?.mapDto.height}
loading="lazy" allowFullScreen={contact?.mapDto.allowFullScreen}
referrerPolicy="no-referrer-when-downgrade" loading={contact?.mapDto.loading as 'lazy' | 'eager' | undefined}
referrerPolicy={
contact?.mapDto.referrerPolicy as React.HTMLAttributeReferrerPolicy | undefined
}
></iframe> ></iframe>
</div> </div>
</div> </div>

View file

@ -19,128 +19,7 @@ import navigationIcon from '@/configs/navigation-icon.config'
const Services: React.FC = () => { const Services: React.FC = () => {
const { translate } = useLocalization() const { translate } = useLocalization()
const [services, setServices] = useState<ServiceDto[]>([]) const [services, setServices] = useState<ServiceDto[]>([])
const [loading, setLoading] = useState(true)
// const services: ServiceDto[] = [
// {
// icon: <FaCode className="w-12 h-12 text-blue-600" />,
// title: translate('::Public.services.software.title'),
// description: translate('::Public.services.software.desc'),
// type: 'service',
// features: [
// translate('::Public.services.software.features.analysis'),
// translate('::Public.services.software.features.design'),
// translate('::Public.services.software.features.development'),
// translate('::Public.services.software.features.testing'),
// translate('::Public.services.software.features.maintenance'),
// ],
// },
// {
// icon: <FaUsers className="w-12 h-12 text-purple-600" />,
// title: translate('::Public.services.web.title'),
// description: translate('::Public.services.web.desc'),
// type: 'service',
// features: [
// translate('::Public.services.web.features.frontend'),
// translate('::Public.services.web.features.backend'),
// translate('::Public.services.web.features.api'),
// translate('::Public.services.web.features.seo'),
// translate('::Public.services.web.features.performance'),
// ],
// },
// {
// icon: <FaShieldAlt className="w-12 h-12 text-green-600" />,
// title: translate('::Public.services.mobile.title'),
// description: translate('::Public.services.mobile.desc'),
// type: 'service',
// features: [
// translate('::Public.services.mobile.features.design'),
// translate('::Public.services.mobile.features.native'),
// translate('::Public.services.mobile.features.cross'),
// translate('::Public.services.mobile.features.push'),
// translate('::Public.services.mobile.features.store'),
// ],
// },
// {
// icon: <FaServer className="w-12 h-12 text-red-600" />,
// title: translate('::Public.services.database.title'),
// description: translate('::Public.services.database.desc'),
// type: 'service',
// features: [
// translate('::Public.services.database.features.design'),
// translate('::Public.services.database.features.optimization'),
// translate('::Public.services.database.features.migration'),
// translate('::Public.services.database.features.backup'),
// translate('::Public.services.database.features.recovery'),
// ],
// },
// {
// icon: <FaGlobe className="w-12 h-12 text-yellow-600" />,
// title: translate('::Public.services.integration.title'),
// description: translate('::Public.services.integration.desc'),
// type: 'service',
// features: [
// translate('::Public.services.integration.features.api'),
// translate('::Public.services.integration.features.middleware'),
// translate('::Public.services.integration.features.legacy'),
// translate('::Public.services.integration.features.realtime'),
// translate('::Public.services.integration.features.monitoring'),
// ],
// },
// {
// icon: <FaCog className="w-12 h-12 text-indigo-600" />,
// title: translate('::Public.services.consulting.title'),
// description: translate('::Public.services.consulting.desc'),
// type: 'service',
// features: [
// translate('::Public.services.consulting.features.tech'),
// translate('::Public.services.consulting.features.project'),
// translate('::Public.services.consulting.features.digital'),
// translate('::Public.services.consulting.features.risk'),
// translate('::Public.services.consulting.features.training'),
// ],
// },
// {
// icon: <FaUsers className="w-12 h-12 text-pink-600" />, // Remote Branch Support
// title: translate('::Public.services.support.branchRemote.title'),
// description: '',
// type: 'support',
// features: [
// translate('::Public.services.support.branchRemote.features.priority'),
// translate('::Public.services.support.branchRemote.features.remote'),
// translate('::Public.services.support.branchRemote.features.optimization'),
// translate('::Public.services.support.branchRemote.features.maintenance'),
// translate('::Public.services.support.branchRemote.features.consulting'),
// ],
// },
// {
// icon: <FaServer className="w-12 h-12 text-orange-600" />, // Backup Support
// title: translate('::Public.services.support.backup.title'),
// description: '',
// type: 'support',
// features: [
// translate('::Public.services.support.backup.features.daily'),
// translate('::Public.services.support.backup.features.encrypted'),
// translate('::Public.services.support.backup.features.recovery'),
// translate('::Public.services.support.backup.features.verification'),
// translate('::Public.services.support.backup.features.access'),
// ],
// },
// {
// icon: <FaGlobe className="w-12 h-12 text-cyan-600" />, // SMS Support
// title: translate('::Public.services.support.sms.title'),
// description: '',
// type: 'support',
// features: [
// translate('::Public.services.support.sms.features.packages'),
// translate('::Public.services.support.sms.features.bulk'),
// translate('::Public.services.support.sms.features.template'),
// translate('::Public.services.support.sms.features.reporting'),
// translate('::Public.services.support.sms.features.api'),
// ],
// },
// ]
// Botları çek
const iconColors = [ const iconColors = [
'text-blue-600', 'text-blue-600',
@ -156,6 +35,8 @@ const Services: React.FC = () => {
} }
useEffect(() => { useEffect(() => {
setLoading(true)
const fetchServices = async () => { const fetchServices = async () => {
try { try {
const result = await getServices() const result = await getServices()
@ -170,6 +51,8 @@ const Services: React.FC = () => {
setServices(items) setServices(items)
} catch (error) { } catch (error) {
console.error('Service listesi alınırken hata oluştu:', error) console.error('Service listesi alınırken hata oluştu:', error)
} finally {
setLoading(false)
} }
} }