Dil Desteği ve Seederlar

This commit is contained in:
Sedat Öztürk 2025-08-17 22:07:04 +03:00
parent dd7458093c
commit 284d0d777c
16 changed files with 1252 additions and 334 deletions

View file

@ -12186,8 +12186,8 @@ public class ListFormsSeeder : IDataSeedContributor, ITransientDependency
ItemType = "group",
Items =
[
new EditingFormItemDto { Order = 1, DataField = "Name", ColSpan = 2, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 2, DataField = "Description", ColSpan = 2, EditorType2 = EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 1, DataField = "Name", ColSpan = 2, IsRequired = true, EditorType2 = EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 2, DataField = "Description", ColSpan = 2, EditorType2 = EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 3, DataField = "Category", ColSpan = 2, EditorType2 = EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 4, DataField = "MonthlyPrice", ColSpan = 2, EditorType2 = EditorTypes.dxNumberBox },
new EditingFormItemDto { Order = 5, DataField = "YearlyPrice", ColSpan = 2, EditorType2 = EditorTypes.dxNumberBox },
@ -12262,7 +12262,14 @@ public class ListFormsSeeder : IDataSeedContributor, ITransientDependency
E = true,
I = true,
Deny = false
})
}),
LookupJson = JsonSerializer.Serialize(new LookupDto
{
DataSourceType = UiLookupDataSourceTypeEnum.Query,
DisplayExpr = "Name",
ValueExpr = "Key",
LookupQuery = lookupQueryLanguageKeyValues
}),
},
new()
{
@ -12284,7 +12291,14 @@ public class ListFormsSeeder : IDataSeedContributor, ITransientDependency
E = true,
I = true,
Deny = false
})
}),
LookupJson = JsonSerializer.Serialize(new LookupDto
{
DataSourceType = UiLookupDataSourceTypeEnum.Query,
DisplayExpr = "Name",
ValueExpr = "Key",
LookupQuery = lookupQueryLanguageKeyValues
}),
},
new()
{
@ -12304,9 +12318,9 @@ public class ListFormsSeeder : IDataSeedContributor, ITransientDependency
DisplayExpr = "name",
ValueExpr = "key",
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
new () { Key="Üyelik",Name="Üyelik" },
new () { Key="Lisans",Name="Lisans" },
new () { Key="Ek Hizmetler",Name="Ek Hizmetler" },
new () { Key="Public.products.categories.Üyelik",Name="Üyelik" },
new () { Key="Public.products.categories.Lisans",Name="Lisans" },
new () { Key="Public.products.categories.Ek Hizmetler",Name="Ek Hizmetler" },
}),
}),
ValidationRuleJson = JsonSerializer.Serialize(new[]

View file

@ -7189,6 +7189,600 @@
"tr": "Demo Talep Edin",
"en": "Request a Demo"
},
{
"resourceName": "Platform",
"key": "Public.products.billingcycle",
"tr": "Faturalama Döngüsü",
"en": "Billing Cycle"
},
{
"resourceName": "Platform",
"key": "Public.products.period",
"tr": "Periyot",
"en": "Period"
},
{
"resourceName": "Platform",
"key": "Public.products.billingcycle.yearly",
"tr": "Yıllık",
"en": "Yearly"
},
{
"resourceName": "Platform",
"key": "Public.products.billingcycle.monthly",
"tr": "Aylık",
"en": "Monthly"
},
{
"resourceName": "Platform",
"key": "Public.products.billingcycle.year",
"tr": "Yıl",
"en": "Year"
},
{
"resourceName": "Platform",
"key": "Public.products.billingcycle.month",
"tr": "Ay",
"en": "Month"
},
{
"resourceName": "Platform",
"key": "Public.products.categories",
"tr": "Kategoriler",
"en": "Categories"
},
{
"resourceName": "Platform",
"key": "Public.products.categories.all",
"tr": "Tüm Ürünler",
"en": "All Products"
},
{
"resourceName": "Platform",
"key": "Public.products.categories.Üyelik",
"tr": "Üyelik",
"en": "Membership"
},
{
"resourceName": "Platform",
"key": "Public.products.categories.Lisans",
"tr": "Lisans",
"en": "License"
},
{
"resourceName": "Platform",
"key": "Public.products.categories.Ek Hizmetler",
"tr": "Ek Hizmetler",
"en": "Additional Services"
},
{
"resourceName": "Platform",
"key": "Public.order.success.title",
"tr": "Siparişiniz Başarıyla Alındı!",
"en": "Your order has been received successfully!"
},
{
"resourceName": "Platform",
"key": "Public.order.success.number",
"tr": "Sipariş numaranız:",
"en": "Your order number:"
},
{
"resourceName": "Platform",
"key": "Public.order.success.nextSteps",
"tr": "Sonraki Adımlar",
"en": "Next Steps"
},
{
"resourceName": "Platform",
"key": "Public.order.success.step1",
"tr": "Siparişiniz 24 saat içinde işleme alınacaktır",
"en": "Your order will be processed within 24 hours"
},
{
"resourceName": "Platform",
"key": "Public.order.success.step2",
"tr": "Lisans bilgileri e-posta adresinize gönderilecektir",
"en": "License details will be sent to your email address"
},
{
"resourceName": "Platform",
"key": "Public.order.success.step3",
"tr": "Kurulum desteği için ekibimizle iletişime geçebilirsiniz",
"en": "You can contact our team for installation support"
},
{
"resourceName": "Platform",
"key": "Public.order.success.backHome",
"tr": "Ana Sayfa'ya Dön",
"en": "Back to Home"
},
{
"resourceName": "Platform",
"key": "Public.payment.loading",
"tr": "Yükleniyor...",
"en": "Loading..."
},
{
"resourceName": "Platform",
"key": "Public.payment.customerInfo",
"tr": "Müşteri Bilgileri",
"en": "Customer Information"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.code",
"tr": "Kurum Kodu:",
"en": "Institution Code:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.founder",
"tr": "Kurucu:",
"en": "Founder:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.company",
"tr": "Şirket:",
"en": "Company:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.email",
"tr": "E-posta:",
"en": "Email:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.phone",
"tr": "Telefon:",
"en": "Phone:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.address",
"tr": "Adres:",
"en": "Address:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.country",
"tr": "Ülke:",
"en": "Country:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.city",
"tr": "Şehir:",
"en": "City:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.district",
"tr": "İlçe:",
"en": "District:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.postalCode",
"tr": "Posta Kodu:",
"en": "Postal Code:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.taxOffice",
"tr": "Vergi Dairesi:",
"en": "Tax Office:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.taxNumber",
"tr": "Vergi No:",
"en": "Tax Number:"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.reference",
"tr": "Referans:",
"en": "Reference:"
},
{
"resourceName": "Platform",
"key": "Public.payment.method.title",
"tr": "Ödeme Yöntemi",
"en": "Payment Method"
},
{
"resourceName": "Platform",
"key": "Public.payment.method.installmentsAvailable",
"tr": "Taksit seçenekleri mevcut",
"en": "Installment options available"
},
{
"resourceName": "Platform",
"key": "Public.payment.method.noCommission",
"tr": "Komisyon yok",
"en": "No commission"
},
{
"resourceName": "Platform",
"key": "Public.payment.installments.title",
"tr": "Taksit Seçenekleri",
"en": "Installment Options"
},
{
"resourceName": "Platform",
"key": "Public.payment.installments.commission",
"tr": "Komisyon:",
"en": "Commission:"
},
{
"resourceName": "Platform",
"key": "Public.payment.installments.monthly",
"tr": "x aylık",
"en": "x monthly"
},
{
"resourceName": "Platform",
"key": "Public.payment.installments.single",
"tr": "Tek ödeme",
"en": "Single Payment"
},
{
"resourceName": "Platform",
"key": "Public.payment.card.title",
"tr": "Kart Bilgileri",
"en": "Card Information"
},
{
"resourceName": "Platform",
"key": "Public.payment.card.name",
"tr": "Kart Üzerindeki İsim",
"en": "Name on Card"
},
{
"resourceName": "Platform",
"key": "Public.payment.card.number",
"tr": "Kart Numarası",
"en": "Card Number"
},
{
"resourceName": "Platform",
"key": "Public.payment.summary.title",
"tr": "Sipariş Özeti",
"en": "Order Summary"
},
{
"resourceName": "Platform",
"key": "Public.payment.summary.subtotal",
"tr": "Ara Toplam:",
"en": "Subtotal:"
},
{
"resourceName": "Platform",
"key": "Public.payment.summary.commission",
"tr": "Komisyon:",
"en": "Commission:"
},
{
"resourceName": "Platform",
"key": "Public.payment.summary.monthlyInstallment",
"tr": "Aylık Taksit:",
"en": "Monthly Installment:"
},
{
"resourceName": "Platform",
"key": "Public.payment.summary.total",
"tr": "Toplam:",
"en": "Total:"
},
{
"resourceName": "Platform",
"key": "Public.payment.buttons.back",
"tr": "Geri",
"en": "Back"
},
{
"resourceName": "Platform",
"key": "Public.payment.buttons.completeOrder",
"tr": "Siparişi Tamamla",
"en": "Complete Order"
},
{
"resourceName": "Platform",
"key": "Public.payment.buttons.pay",
"tr": "Ödeme Yap",
"en": "Pay"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.customerInfo",
"tr": "Müşteri Bilgileri",
"en": "Customer Information"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.existing.title",
"tr": "Mevcut Müşteri",
"en": "Existing Customer"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.existing.desc",
"tr": "Kurum kodunuz ile giriş yapın",
"en": "Log in with your organization code"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.new.title",
"tr": "Yeni Müşteri",
"en": "New Customer"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.new.desc",
"tr": "Kayıt olun ve hemen başlayın",
"en": "Register and get started immediately"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.orgCode",
"tr": "Kurum Kodu *",
"en": "Organization Code *"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.orgName",
"tr": "Şirket Adı *",
"en": "Company Name *"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.searchOrg",
"tr": "Kurumu Bul",
"en": "Find Organization"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.founder",
"tr": "Kurucu:",
"en": "Founder:"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.company",
"tr": "Şirket:",
"en": "Company:"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.email",
"tr": "E-posta:",
"en": "Email:"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.phone",
"tr": "Telefon *",
"en": "Phone *"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.mobile",
"tr": "Mobile:",
"en": "Mobile:"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.address",
"tr": "Adres *",
"en": "Address *"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.country",
"tr": "Ülke",
"en": "Country"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.city",
"tr": "Şehir *",
"en": "City *"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.district",
"tr": "İlçe *",
"en": "District *"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.postalCode",
"tr": "Posta Kodu *",
"en": "Postal Code *"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.taxOffice",
"tr": "Vergi Dairesi *",
"en": "Tax Office *"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.taxNumber",
"tr": "Vergi No *",
"en": "Tax Number *"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.reference",
"tr": "Referans",
"en": "Reference"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.buttons.back",
"tr": "Geri",
"en": "Back"
},
{
"resourceName": "Platform",
"key": "Public.products.tenantForm.buttons.continue",
"tr": "Devam Et",
"en": "Continue"
},
{
"resourceName": "Platform",
"key": "Public.products.search.title",
"tr": "Arama",
"en": "Search"
},
{
"resourceName": "Platform",
"key": "Public.products.search.placeholder",
"tr": "Ürün ara...",
"en": "Search products..."
},
{
"resourceName": "Platform",
"key": "Public.products.empty.title",
"tr": "Ürün bulunamadı",
"en": "No products found"
},
{
"resourceName": "Platform",
"key": "Public.products.empty.description",
"tr": "Arama kriterlerinizi değiştirmeyi deneyin.",
"en": "Try changing your search criteria."
},
{
"resourceName": "Platform",
"key": "Public.products.loading",
"tr": "Yükleniyor...",
"en": "Loading..."
},
{
"resourceName": "Platform",
"key": "Public.cart.title",
"tr": "Sepetim",
"en": "My Cart"
},
{
"resourceName": "Platform",
"key": "Public.cart.clearConfirm",
"tr": "Sepetteki tüm ürünleri silmek istediğinizden emin misiniz?",
"en": "Are you sure you want to remove all items from the cart?"
},
{
"resourceName": "Platform",
"key": "Public.cart.clear",
"tr": "Sepeti Temizle",
"en": "Clear Cart"
},
{
"resourceName": "Platform",
"key": "Public.cart.empty.title",
"tr": "Sepetiniz boş",
"en": "Your cart is empty"
},
{
"resourceName": "Platform",
"key": "Public.cart.empty.subtitle",
"tr": "Ürün eklemek için katalogdan seçim yapın",
"en": "Add products from the catalog"
},
{
"resourceName": "Platform",
"key": "Public.cart.monthly",
"tr": "Aylık",
"en": "Monthly"
},
{
"resourceName": "Platform",
"key": "Public.cart.yearly",
"tr": "Yıllık",
"en": "Yearly"
},
{
"resourceName": "Platform",
"key": "Public.cart.month",
"tr": "Ay",
"en": "Month"
},
{
"resourceName": "Platform",
"key": "Public.cart.year",
"tr": "Yıl",
"en": "Year"
},
{
"resourceName": "Platform",
"key": "Public.cart.total",
"tr": "Toplam:",
"en": "Total:"
},
{
"resourceName": "Platform",
"key": "Public.cart.checkout",
"tr": "Ödemeye Geç",
"en": "Proceed to Checkout"
},
{
"resourceName": "Platform",
"key": "Public.products.quantity",
"tr": "Adet:",
"en": "Quantity:"
},
{
"resourceName": "Platform",
"key": "Public.products.savings.yearly",
"tr": "Yıllık ile %{{percent}} tasarruf",
"en": "Save {{percent}}% with yearly"
},
{
"resourceName": "Platform",
"key": "Public.products.price.perMonthSuffix",
"tr": "/ Ay",
"en": "/ Month"
},
{
"resourceName": "Platform",
"key": "Public.products.price.perYearSuffix",
"tr": "/ Yıl",
"en": "/ Year"
},
{
"resourceName": "Platform",
"key": "Public.products.period.month",
"tr": "Ay",
"en": "Month"
},
{
"resourceName": "Platform",
"key": "Public.products.period.year",
"tr": "Yıl",
"en": "Year"
},
{
"resourceName": "Platform",
"key": "Public.products.total",
"tr": "Toplam:",
"en": "Total:"
},
{
"resourceName": "Platform",
"key": "Public.products.addToCart",
"tr": "Sepete Ekle",
"en": "Add to Cart"
},
{
"resourceName": "Platform",
"key": "Public.products.inCart",
"tr": "Sepette Mevcut",
"en": "Already in Cart"
},
{
"resourceName": "Platform",
"key": "Public.products.cta.description",
@ -9408,7 +10002,177 @@
"key": "App.DeveloperKit.ComponentEditor.Loading",
"en": "Loading component...",
"tr": "Bileşen yükleniyor..."
}
},
{
"resourceName": "Platform",
"key": "Public.products.remoteBranchTraining",
"tr": "Uzaktan Şube Eğitimi (5 Saat)",
"en": "Remote Branch Training (5 Hours)"
},
{
"resourceName": "Platform",
"key": "Public.products.remoteBranchTraining.desc",
"tr": "Sistem kullanımı sırasında kullanıcı hataları ortaya çıkabilir, Sözsoft kullanıcı hatalarının giderilmesi yönünde destek hizmeti sunmaktadır.",
"en": "During system usage, user errors may occur. Sözsoft provides support services to resolve user errors."
},
{
"resourceName": "Platform",
"key": "Public.products.branchHosting",
"tr": "Şube Barındırma Hizmeti",
"en": "Branch Hosting Service"
},
{
"resourceName": "Platform",
"key": "Public.products.branchHosting.desc",
"tr": "Şube veya şubelerinize ait tüm bilgilerinizin veya resimlerinizin barındırıldığı alan kiralama hizmetidir.",
"en": "A hosting service where all your branch-related information or images are stored."
},
{
"resourceName": "Platform",
"key": "Public.products.sms100k",
"tr": "100,000 SMS SOFTWARE",
"en": "100,000 SMS SOFTWARE"
},
{
"resourceName": "Platform",
"key": "Public.products.sms100k.desc",
"tr": "http://www.smsyukle.com adresinden yüklenebilir. İhtiyacınız olan SMS adedini belirleyerek, SMS satın almak için lütfen SMS yükle linkine tıklayın veya bize ulaşın.",
"en": "Available at http://www.smsyukle.com. Select the number of SMS you need and click the SMS load link or contact us to purchase."
},
{
"resourceName": "Platform",
"key": "Public.products.teacherLicense",
"tr": "Öğretmen Lisansı",
"en": "Teacher License"
},
{
"resourceName": "Platform",
"key": "Public.products.teacherLicense.desc",
"tr": "Şube/şubelerinizde sistemin kaç öğretmen tarafından kullandırılacağı kararına bağlı olarak belirlenir. Öğretmen lisansları aylık/yıllık kullanım olarak sunulmaktadır.",
"en": "Determined by how many teachers in your branch/branches will use the system. Teacher licenses are offered monthly/annually."
},
{
"resourceName": "Platform",
"key": "Public.products.extraHourlyService",
"tr": "Saatlik Ek Hizmet",
"en": "Extra Hourly Service"
},
{
"resourceName": "Platform",
"key": "Public.products.extraHourlyService.desc",
"tr": "Şubelerin standart hizmetlerin dışında istedikleri özel çalışmaları için belirlenen saatlik hizmet bedelidir.",
"en": "Hourly service fee for special work requested outside the standard services of branches."
},
{
"resourceName": "Platform",
"key": "Public.products.sms50k",
"tr": "50,000 SMS SOFTWARE",
"en": "50,000 SMS SOFTWARE"
},
{
"resourceName": "Platform",
"key": "Public.products.sms50k.desc",
"tr": "http://www.smsyukle.com adresinden yüklenebilir. İhtiyacınız olan SMS adedini belirleyerek, SMS satın almak için lütfen SMS yükle linkine tıklayın veya bize ulaşın.",
"en": "Available at http://www.smsyukle.com. Select the number of SMS you need and click the SMS load link or contact us to purchase."
},
{
"resourceName": "Platform",
"key": "Public.products.sms25k",
"tr": "25,000 SMS SOFTWARE",
"en": "25,000 SMS SOFTWARE"
},
{
"resourceName": "Platform",
"key": "Public.products.sms25k.desc",
"tr": "http://www.smsyukle.com adresinden yüklenebilir. İhtiyacınız olan SMS adedini belirleyerek, SMS satın almak için lütfen SMS yükle linkine tıklayın veya bize ulaşın.",
"en": "Available at http://www.smsyukle.com. Select the number of SMS you need and click the SMS load link or contact us to purchase."
},
{
"resourceName": "Platform",
"key": "Public.products.sms10k",
"tr": "10,000 SMS SOFTWARE",
"en": "10,000 SMS SOFTWARE"
},
{
"resourceName": "Platform",
"key": "Public.products.sms10k.desc",
"tr": "http://www.smsyukle.com adresinden yüklenebilir. İhtiyacınız olan SMS adedini belirleyerek, SMS satın almak için lütfen SMS yükle linkine tıklayın veya bize ulaşın.",
"en": "Available at http://www.smsyukle.com. Select the number of SMS you need and click the SMS load link or contact us to purchase."
},
{
"resourceName": "Platform",
"key": "Public.products.sms5k",
"tr": "5,000 SMS SOFTWARE",
"en": "5,000 SMS SOFTWARE"
},
{
"resourceName": "Platform",
"key": "Public.products.sms5k.desc",
"tr": "http://www.smsyukle.com adresinden yüklenebilir. İhtiyacınız olan SMS adedini belirleyerek, SMS satın almak için lütfen SMS yükle linkine tıklayın veya bize ulaşın.",
"en": "Available at http://www.smsyukle.com. Select the number of SMS you need and click the SMS load link or contact us to purchase."
},
{
"resourceName": "Platform",
"key": "Public.products.smsBlocking",
"tr": "SMS Engelleme Hizmeti",
"en": "SMS Blocking Service"
},
{
"resourceName": "Platform",
"key": "Public.products.smsBlocking.desc",
"tr": "Şube/şubelerinizden gönderilen SMS'lerin kursiyerler tarafından engelleme hizmetidir.",
"en": "A service that allows recipients to block SMS messages sent from your branch/branches."
},
{
"resourceName": "Platform",
"key": "Public.products.backupService",
"tr": "Veri Yedekleme Hizmeti",
"en": "Data Backup Service"
},
{
"resourceName": "Platform",
"key": "Public.products.backupService.desc",
"tr": "Bilgileriniz bizler için de önemlidir. Bu doğrultuda aldığımız yüksek güvenlik önlemlerinin yanısıra BACK-UP/ yedekleme hizmeti sunmaktayız.",
"en": "Your data is also important to us. In addition to our high security measures, we provide a BACK-UP/backup service."
},
{
"resourceName": "Platform",
"key": "Public.products.userLicense",
"tr": "Kullanıcı Lisansı",
"en": "User License"
},
{
"resourceName": "Platform",
"key": "Public.products.userLicense.desc",
"tr": "Şube/şubelerinizde sistemin kaç personel tarafından kullandırılacağı kararına bağlı olarak belirlenir. Kullanıcı lisansları aylık/yıllık kullanım olarak sunulmaktadır.",
"en": "Determined by how many personnel in your branch/branches will use the system. User licenses are offered monthly/annually."
},
{
"resourceName": "Platform",
"key": "Public.products.remoteSupportContract",
"tr": "Şube Uzaktan Destek Sözleşmesi",
"en": "Branch Remote Support Contract"
},
{
"resourceName": "Platform",
"key": "Public.products.remoteSupportContract.desc",
"tr": "Kullanıcıların/Şubelerin talebi doğrultusunda imzalanan servis sözleşmesi ile Sözsoft kapsamı ve detayı sözleşmelerimizde yer alan iş birliği hizmeti sağlamaktadır.",
"en": "With a service contract signed upon user/branch request, Sözsoft provides cooperation services as detailed in our agreements."
},
{
"resourceName": "Platform",
"key": "Public.products.mobileReporting",
"tr": "Mobil Raporlama Arayüzü",
"en": "Mobile Reporting Interface"
},
{
"resourceName": "Platform",
"key": "Public.products.mobileReporting.desc",
"tr": "kursyazilimi.com mobil arayüzü ile şubeler bazında akıllı telefon teknolojisi ile yönetim imkanı sağlamaktadır.",
"en": "With kursyazilimi.coms mobile interface, management is possible per branch using smartphone technology."
}
],
"Settings": [
{
@ -26730,9 +27494,9 @@
"Products": [
{
"id": "5f4d6c1f-b1e0-4f91-854c-1d59c25e7193",
"name": "Şube Barındırma Hizmeti",
"description": "Şube veya şubelerinize ait tüm bilgilerinizin veya resimlerinizin barındırıldığı alan kiralama hizmetidir.",
"category": "Üyelik",
"name": "Public.products.branchHosting",
"description": "Public.products.branchHosting.desc",
"category": "Public.products.categories.Üyelik",
"order": 1,
"monthlyPrice": 850,
"yearlyPrice": 3400,
@ -26741,9 +27505,9 @@
},
{
"id": "a85d0f04-7d40-47cb-bcf6-d95fbe31ec93",
"name": "Veri Yedekleme Hizmeti",
"description": "Bilgileriniz bizler için de önemlidir. Bu doğrultuda aldığımız yüksek güvenlik önlemlerinin yanısıra BACK-UP/ yedekleme hizmeti sunmaktayız.",
"category": "Üyelik",
"name": "Public.products.backupService",
"description": "Public.products.backupService.desc",
"category": "Public.products.categories.Üyelik",
"order": 2,
"monthlyPrice": 2100,
"yearlyPrice": 2100,
@ -26752,9 +27516,9 @@
},
{
"id": "03cfae0b-4e3a-4b4b-917f-f798f18e0f15",
"name": "Şube Uzaktan Destek Sözleşmesi",
"description": "Kullanıcıların/Şubelerin talebi doğrultusunda imzalanan servis sözleşmesi ile Sözsoft kapsamı ve detayı sözleşmelerimizde yer alan iş birliği hizmeti sağlamaktadır.",
"category": "Üyelik",
"name": "Public.products.remoteSupportContract",
"description": "Public.products.remoteSupportContract.desc",
"category": "Public.products.categories.Üyelik",
"order": 3,
"monthlyPrice": 5000,
"yearlyPrice": 5000,
@ -26763,9 +27527,9 @@
},
{
"id": "03cfae0b-4e3a-4b4b-917f-f798f18e0f11",
"name": "Kullanıcı Lisansı",
"description": "Şube/şubelerinizde sistemin kaç personel tarafından kullandırılacağı kararına bağlı olarak belirlenir. Kullanıcı lisansları aylık/yıllık kullanım olarak sunulmaktadır.",
"category": "Lisans",
"name": "Public.products.userLicense",
"description": "Public.products.userLicense.desc",
"category": "Public.products.categories.Lisans",
"order": 4,
"monthlyPrice": 900,
"yearlyPrice": 3500,
@ -26774,9 +27538,9 @@
},
{
"id": "36d98c72-6a62-4fa1-b942-2689eb42e4d4",
"name": "Öğretmen Lisansı",
"description": "Şube/şubelerinizde sistemin kaç öğretmen tarafından kullandırılacağı kararına bağlı olarak belirlenir. Öğretmen lisansları aylık/yıllık kullanım olarak sunulmaktadır.",
"category": "Lisans",
"name": "Public.products.teacherLicense",
"description": "Public.products.teacherLicense.desc",
"category": "Public.products.categories.Lisans",
"order": 5,
"monthlyPrice": 500,
"yearlyPrice": 1700,
@ -26785,9 +27549,9 @@
},
{
"id": "9a80f69d-46e5-4b92-91dc-fbb4d2f90c1f",
"name": "Mobil Raporlama Arayüzü",
"description": "kursyazilimi.com mobil arayüzü ile şubeler bazında akıllı telefon teknolojisi ile yönetim imkanı sağlamaktadır.",
"category": "Lisans",
"name": "Public.products.mobileReporting",
"description": "Public.products.mobileReporting.desc",
"category": "Public.products.categories.Lisans",
"order": 6,
"monthlyPrice": 400,
"yearlyPrice": 1400,
@ -26796,9 +27560,9 @@
},
{
"id": "66324548-8500-4f06-8b1c-1a31e8d25c39",
"name": "Uzaktan Şube Eğitimi (5 Saat)",
"description": "Sistem kullanımı sırasında kullanıcı hataları ortaya çıkabilir, sözsoft kullanıcı hatalarının giderilmesi yönünde destek hizmeti sunmaktadır.",
"category": "Ek Hizmetler",
"name": "Public.products.remoteBranchTraining",
"description": "Public.products.remoteBranchTraining.desc",
"category": "Public.products.categories.Ek Hizmetler",
"order": 7,
"monthlyPrice": 3700,
"yearlyPrice": 3700,
@ -26807,10 +27571,10 @@
},
{
"id": "b0b51a46-cf33-423f-b93f-2e2a3b8e27c0",
"name": "Saatlik Ek Hizmet",
"description": "Şubelerin standart hizmetlerin dışında istedikleri özel çalışmaları için belirlenen saatlik hizmet bedelidir.",
"name": "Public.products.extraHourlyService",
"description": "Public.products.extraHourlyService.desc",
"order": 8,
"category": "Ek Hizmetler",
"category": "Public.products.categories.Ek Hizmetler",
"monthlyPrice": 2000,
"yearlyPrice": 2000,
"isQuantityBased": true,
@ -26818,9 +27582,9 @@
},
{
"id": "15b813c6-4905-412b-999a-c303b91b3152",
"name": "5,000 SMS SOFTWARE",
"description": "http://www.smsyukle.com adresinden yüklenebilir. İhtiyacınız olan SMS adedini belirleyerek, SMS satın almak için lütfen SMS yükle linkine tıklayın veya bize ulaşın",
"category": "Ek Hizmetler",
"name": "Public.products.sms5k",
"description": "Public.products.sms5k.desc",
"category": "Public.products.categories.Ek Hizmetler",
"order": 9,
"monthlyPrice": 750,
"yearlyPrice": 750,
@ -26829,9 +27593,9 @@
},
{
"id": "e2c940b4-4a35-4f3d-8600-b1c2b9ce5179",
"name": "10,000 SMS SOFTWARE",
"description": "http://www.smsyukle.com adresinden yüklenebilir. İhtiyacınız olan SMS adedini belirleyerek, SMS satın almak için lütfen SMS yükle linkine tıklayın veya bize ulaşın",
"category": "Ek Hizmetler",
"name": "Public.products.sms10k",
"description": "Public.products.sms10k.desc",
"category": "Public.products.categories.Ek Hizmetler",
"order": 10,
"monthlyPrice": 1350,
"yearlyPrice": 1350,
@ -26840,9 +27604,9 @@
},
{
"id": "1985ba1b1-f4c6-40f2-a747-0cf45c96a5b7",
"name": "25,000 SMS SOFTWARE",
"description": "http://www.smsyukle.com adresinden yüklenebilir. İhtiyacınız olan SMS adedini belirleyerek, SMS satın almak için lütfen SMS yükle linkine tıklayın veya bize ulaşın",
"category": "Ek Hizmetler",
"name": "Public.products.sms25k",
"description": "Public.products.sms25k.desc",
"category": "Public.products.categories.Ek Hizmetler",
"order": 11,
"monthlyPrice": 2900,
"yearlyPrice": 2900,
@ -26851,9 +27615,9 @@
},
{
"id": "7a7ae7c3-bef2-4978-90cf-96d1475e3492",
"name": "50,000 SMS SOFTWARE",
"description": "http://www.smsyukle.com adresinden yüklenebilir. İhtiyacınız olan SMS adedini belirleyerek, SMS satın almak için lütfen SMS yükle linkine tıklayın veya bize ulaşın",
"category": "Ek Hizmetler",
"name": "Public.products.sms50k",
"description": "Public.products.sms50k.desc",
"category": "Public.products.categories.Ek Hizmetler",
"order": 12,
"monthlyPrice": 5100,
"yearlyPrice": 5100,
@ -26862,9 +27626,9 @@
},
{
"id": "3e6a8de1-6c48-4ff8-87f5-252735a7e89f",
"name": "100,000 SMS SOFTWARE",
"description": "http://www.smsyukle.com adresinden yüklenebilir. İhtiyacınız olan SMS adedini belirleyerek, SMS satın almak için lütfen SMS yükle linkine tıklayın veya bize ulaşın",
"category": "Ek Hizmetler",
"name": "Public.products.sms100k",
"description": "Public.products.sms100k.desc",
"category": "Public.products.categories.Ek Hizmetler",
"order": 13,
"monthlyPrice": 8800,
"yearlyPrice": 8800,
@ -26873,9 +27637,9 @@
},
{
"id": "2e35b9b8-f404-4b83-9737-d059c05fd44b",
"name": "SMS Engelleme Hizmeti",
"description": "Şube/şubelerinizden gönderilen SMS lerin kursiyerler tarafından engelleme hizmetidir.",
"category": "Ek Hizmetler",
"name": "Public.products.smsBlocking",
"description": "Public.products.smsBlocking.desc",
"category": "Public.products.categories.Ek Hizmetler",
"order": 14,
"monthlyPrice": 880,
"yearlyPrice": 880,

View file

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

View file

@ -1,5 +1,5 @@
import React from 'react'
import { FaCalendar, FaClock, FaMinus, FaPlus, FaShoppingCart } from 'react-icons/fa';
import { FaCalendar, FaClock, FaMinus, FaPlus, FaShoppingCart } from 'react-icons/fa'
import { BillingCycle } from '@/proxy/order/models'
import { CartState } from '@/utils/cartUtils'
import { useScroll } from '@/contexts/ScrollContext'
@ -42,7 +42,9 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
<div className="flex items-center space-x-6">
<div className="flex items-center space-x-2 rounded-lg">
<FaCalendar className="w-5 h-5 text-gray-600" />
<span className="font-medium text-gray-900">Faturalama Döngüsü:</span>
<span className="font-medium text-gray-900">
{translate('::Public.products.billingcycle')}
</span>
</div>
<div className="flex space-x-2">
@ -60,7 +62,7 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
hasCartItems ? 'Sepette ürün varken faturalama döngüsü değiştirilemez' : undefined
}
>
Aylık
{translate('::Public.products.billingcycle.monthly')}
</button>
<button
onClick={() => !hasCartItems && setGlobalBillingCycle('yearly')}
@ -76,7 +78,7 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
hasCartItems ? 'Sepette ürün varken faturalama döngüsü değiştirilemez' : undefined
}
>
Yıllık
{translate('::Public.products.billingcycle.yearly')}
</button>
</div>
</div>
@ -85,7 +87,9 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-2">
<FaClock className="w-5 h-5 text-gray-600" />
<span className="font-medium text-gray-900">Periyod:</span>
<span className="font-medium text-gray-900">
{translate('::Public.products.period')}
</span>
</div>
<div className="flex items-center space-x-2">
@ -105,7 +109,9 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
<div className="flex items-center space-x-2 bg-gray-50 px-4 py-2 rounded-lg">
<span className="font-bold text-lg text-blue-600">{globalPeriod}</span>
<span className="text-sm text-gray-600">
{globalBillingCycle === 'monthly' ? 'Ay' : 'Yıl'}
{globalBillingCycle === 'monthly'
? translate('::Public.products.billingcycle.month')
: translate('::Public.products.billingcycle.year')}
</span>
</div>
@ -139,16 +145,6 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
</button>
</div>
</div>
{/* {globalBillingCycle === 'yearly' && globalPeriod > 1 && (
<div className="mt-3 text-center">
<div className="inline-flex items-center px-3 py-1 bg-emerald-100 text-emerald-800 text-sm font-medium rounded-full">
<span>
{globalPeriod} yıllık abonelik ile %{Math.round((1 - (globalBillingCycle === 'yearly' ? 1 : 12)) * 100)} tasarruf
</span>
</div>
</div>
)} */}
</div>
</div>
)

View file

@ -1,28 +1,23 @@
import React from 'react';
import {
FaTimes,
FaMinus,
FaPlus,
FaShoppingBag,
FaTrash
} from 'react-icons/fa';
import { BillingCycle, BasketItem } from '@/proxy/order/models';
import React from 'react'
import { FaTimes, FaMinus, FaPlus, FaShoppingBag, FaTrash } from 'react-icons/fa'
import { BillingCycle, BasketItem } from '@/proxy/order/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface CartState {
items: BasketItem[];
total: number;
globalBillingCycle: BillingCycle;
globalPeriod: number;
items: BasketItem[]
total: number
globalBillingCycle: BillingCycle
globalPeriod: number
}
interface CartProps {
isOpen: boolean;
onClose: () => void;
onCheckout: () => void;
cartState: CartState;
updateQuantity: (id: string, quantity: number) => void;
removeItem: (id: string) => void;
clearCart: () => void;
isOpen: boolean
onClose: () => void
onCheckout: () => void
cartState: CartState
updateQuantity: (id: string, quantity: number) => void
removeItem: (id: string) => void
clearCart: () => void
}
export const Cart: React.FC<CartProps> = ({
@ -32,24 +27,25 @@ export const Cart: React.FC<CartProps> = ({
cartState,
updateQuantity,
removeItem,
clearCart
clearCart,
}) => {
const { translate } = useLocalization()
const handleClearCart = () => {
if (window.confirm('Sepetteki tüm ürünleri silmek istediğinizden emin misiniz?')) {
clearCart();
clearCart()
}
};
}
const formatPrice = (price: number) => {
return new Intl.NumberFormat('tr-TR', {
style: 'currency',
currency: 'TRY',
minimumFractionDigits: 0
}).format(price);
};
minimumFractionDigits: 0,
}).format(price)
}
if (!isOpen) return null;
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 overflow-hidden">
@ -58,13 +54,15 @@ export const Cart: React.FC<CartProps> = ({
<div className="absolute right-0 top-0 h-full w-full max-w-md bg-white shadow-xl">
<div className="flex flex-col h-full">
<div className="flex items-center justify-between p-6 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">Sepetim</h2>
<h2 className="text-lg font-semibold text-gray-900">
{translate('::Public.cart.title')}
</h2>
<div className="flex items-center space-x-2">
{cartState.items.length > 0 && (
<button
onClick={handleClearCart}
className="p-2 text-red-500 hover:bg-red-50 rounded-lg transition-colors"
title="Sepeti Temizle"
title={translate('::Public.cart.clear')}
>
<FaTrash className="w-5 h-5" />
</button>
@ -82,16 +80,19 @@ export const Cart: React.FC<CartProps> = ({
{cartState.items.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-gray-500">
<FaShoppingBag className="w-16 h-16 mb-4" />
<p className="text-lg font-medium">Sepetiniz boş</p>
<p className="text-sm">Ürün eklemek için katalogdan seçim yapın</p>
<p className="text-lg font-medium">{translate('::Public.cart.empty.title')}</p>
<p className="text-sm">{translate('::Public.cart.empty.subtitle')}</p>
</div>
) : (
<div className="space-y-4">
{cartState.items.map((item: BasketItem, index: number) => (
<div key={`${item.product.id}-${item.billingCycle}-${index}`} className="border border-gray-200 rounded-lg p-4">
<div
key={`${item.product.id}-${item.billingCycle}-${index}`}
className="border border-gray-200 rounded-lg p-4"
>
<div className="flex justify-between items-start mb-2">
<h4 className="font-medium text-gray-900 text-sm leading-tight">
{item.product.name}
{translate('::' + item.product.name)}
</h4>
<button
onClick={() => removeItem(item.product.id)}
@ -106,11 +107,18 @@ export const Cart: React.FC<CartProps> = ({
{item.product.isQuantityBased && item.quantity > 1 && (
<span className="font-medium text-gray-900">{item.quantity}x - </span>
)}
{item.billingCycle === 'monthly' ? 'Aylık' :
item.billingCycle === 'yearly' ? 'Yıllık' : 'Aylık'}
{item.billingCycle === 'monthly'
? translate('::Public.cart.monthly')
: item.billingCycle === 'yearly'
? translate('::Public.cart.yearly')
: translate('::Public.cart.monthly')}
{cartState.globalPeriod > 1 && item.product.isQuantityBased && (
<span className="text-xs text-gray-500">
({cartState.globalPeriod} {item.billingCycle === 'monthly' ? 'Ay' : 'Yıl'})
({cartState.globalPeriod}{' '}
{item.billingCycle === 'monthly'
? translate('::Public.cart.month')
: translate('::Public.cart.year')}
)
</span>
)}
</div>
@ -146,7 +154,9 @@ export const Cart: React.FC<CartProps> = ({
{cartState.items.length > 0 && (
<div className="border-t border-gray-200 p-6">
<div className="flex justify-between items-center mb-4">
<span className="text-lg font-semibold text-gray-900">Toplam:</span>
<span className="text-lg font-semibold text-gray-900">
{translate('::Public.cart.total')}
</span>
<span className="text-xl font-bold text-blue-600">
{formatPrice(cartState.total)}
</span>
@ -156,12 +166,12 @@ export const Cart: React.FC<CartProps> = ({
onClick={onCheckout}
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-4 rounded-lg transition-colors duration-200"
>
Ödemeye Geç
{translate('::Public.cart.checkout')}
</button>
</div>
)}
</div>
</div>
</div>
);
};
)
}

View file

@ -5,6 +5,7 @@ import {
} from 'react-icons/fa';
import { useNavigate } from 'react-router-dom'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization';
interface OrderSuccessProps {
orderId: string
@ -13,24 +14,25 @@ interface OrderSuccessProps {
export const OrderSuccess: React.FC<OrderSuccessProps> = ({ orderId, onBackToShop }) => {
const navigate = useNavigate()
const { translate } = useLocalization()
return (
<div className="max-w-2xl mx-auto text-center">
<div className="bg-white rounded-xl shadow-lg border border-gray-200 p-8">
<div className="mb-6">
<FaCheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
<h2 className="text-2xl font-bold text-gray-900 mb-2">Siparişiniz Başarıyla Alındı!</h2>
<h2 className="text-2xl font-bold text-gray-900 mb-2">{translate('::Public.order.success.title')}</h2>
<p className="text-gray-600">
Sipariş numaranız: <span className="font-semibold text-blue-600">#{orderId}</span>
{translate('::Public.order.success.number')} <span className="font-semibold text-blue-600">#{orderId}</span>
</p>
</div>
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-6">
<h3 className="font-semibold text-green-800 mb-2">Sonraki Adımlar</h3>
<h3 className="font-semibold text-green-800 mb-2">{translate('::Public.order.success.nextSteps')}</h3>
<ul className="text-sm text-green-700 space-y-1 text-left">
<li> Siparişiniz 24 saat içinde işleme alınacaktır</li>
<li> Lisans bilgileri e-posta adresinize gönderilecektir</li>
<li> Kurulum desteği için ekibimizle iletişime geçebilirsiniz</li>
<li> {translate('::Public.order.success.step1')}</li>
<li> {translate('::Public.order.success.step2')}</li>
<li> {translate('::Public.order.success.step3')}</li>
</ul>
</div>
@ -40,7 +42,7 @@ export const OrderSuccess: React.FC<OrderSuccessProps> = ({ orderId, onBackToSho
className="flex items-center justify-center px-6 py-3 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
<FaArrowLeft className="w-4 h-4 mr-2" />
Ana Sayfa'ya Dön
{translate('::Public.order.success.backHome')}
</button>
</div>
</div>

View file

@ -12,8 +12,8 @@ import {
FaGlobe,
FaMoneyBillWave,
FaDollarSign,
FaUserPlus
} from 'react-icons/fa';
FaUserPlus,
} from 'react-icons/fa'
import {
BillingCycle,
BasketItem,
@ -22,6 +22,7 @@ import {
} from '@/proxy/order/models'
import { OrderService } from '@/services/order.service'
import { CustomTenantDto } from '@/proxy/config/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface CartState {
items: BasketItem[]
@ -52,6 +53,7 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
cardName: '',
})
const { translate } = useLocalization()
const [paymentMethods, setPaymentMethods] = useState<PaymentMethodDto[]>([])
const [installmentOptions, setInstallmentOptions] = useState<InstallmentOptionDto[]>([])
const [loading, setLoading] = useState<boolean>(true)
@ -116,7 +118,9 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
return (
<div className="mx-auto px-4 lg:px-8 mb-6">
{loading ? (
<div className="text-center py-12 text-gray-500">Yükleniyor...</div>
<div className="text-center py-12 text-gray-500">
{translate('::Public.payment.loading')}
</div>
) : (
<form onSubmit={handleSubmit} className="flex flex-col lg:flex-row gap-6">
{/* 3 Sütun: Ödeme Yöntemi | Taksit Seçenekleri | Sipariş Özeti */}
@ -125,88 +129,115 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
{tenant && (
<>
<h2 className="text-lg font-semibold mb-4 flex items-center">
<FaUser className="w-5 h-5 text-green-600 mr-2" /> Müşteri Bilgileri
<FaUser className="w-5 h-5 text-green-600 mr-2" />{' '}
{translate('::Public.payment.customerInfo')}
</h2>
<div className="pt-4 border-t border-gray-200">
<div className="grid grid-cols-1 gap-y-3 text-sm text-gray-700">
<div className="flex items-center gap-2">
<FaUser className="w-4 h-4 text-gray-500" />
<span className="font-medium">Kurum Kodu:</span>
<span className="font-medium">
{translate('::Public.payment.customer.code')}
</span>
<span>{tenant.name}</span>
</div>
<div className="flex items-center gap-2">
<FaUser className="w-4 h-4 text-gray-500" />
<span className="font-medium">Kurucu:</span>
<span className="font-medium">
{translate('::Public.payment.customer.founder')}
</span>
<span>{tenant.founder}</span>
</div>
<div className="flex items-center gap-2">
<FaBuilding className="w-4 h-4 text-gray-500" />
<span className="font-medium">Şirket:</span>
<span className="font-medium">
{translate('::Public.payment.customer.company')}
</span>
<span>{tenant.institutionName}</span>
</div>
<div className="flex items-center gap-2">
<FaMailBulk className="w-4 h-4 text-gray-500" />
<span className="font-medium">E-posta:</span>
<span className="font-medium">
{translate('::Public.payment.customer.email')}
</span>
<span>{tenant.email}</span>
</div>
<div className="flex items-center gap-2">
<FaPhone className="w-4 h-4 text-gray-500" />
<span className="font-medium">Telefon:</span>
<span className="font-medium">
{translate('::Public.payment.customer.phone')}
</span>
<span>{tenant.phone}</span>
</div>
<div className="flex items-start gap-2">
<FaMapMarkerAlt className="w-4 h-4 text-gray-500 mt-0.5" />
<div>
<span className="font-medium">Adres:</span>
<span className="font-medium">
{translate('::Public.payment.customer.address')}:
</span>
<div>{tenant.address}</div>
</div>
</div>
<div className="flex items-center gap-2">
<FaGlobe className="w-4 h-4 text-gray-500" />
<span className="font-medium">Ülke:</span>
<span className="font-medium">
{translate('::Public.payment.customer.country')}
</span>
<span>{tenant.country}</span>
</div>
<div className="flex items-center gap-2">
<FaGlobe className="w-4 h-4 text-gray-500" />
<span className="font-medium">Şehir:</span>
<span className="font-medium">
{translate('::Public.payment.customer.city')}
</span>
<span>{tenant.city}</span>
</div>
<div className="flex items-center gap-2">
<FaMap className="w-4 h-4 text-gray-500" />
<span className="font-medium">İlçe:</span>
<span className="font-medium">
{translate('::Public.payment.customer.district')}
</span>
<span>{tenant.district}</span>
</div>
<div className="flex items-center gap-2">
<FaMapMarkerAlt className="w-4 h-4 text-gray-500" />
<span className="font-medium">Posta Kodu:</span>
<span className="font-medium">
{translate('::Public.payment.customer.postalCode')}:
</span>
<span>{tenant.postalCode}</span>
</div>
<div className="flex items-center gap-2">
<FaMoneyBillWave className="w-4 h-4 text-gray-500" />
<span className="font-medium">Vergi Dairesi:</span>
<span className="font-medium">
{translate('::Public.payment.customer.taxOffice')}:
</span>
<span>{tenant.taxOffice}</span>
</div>
<div className="flex items-center gap-2">
<FaDollarSign className="w-4 h-4 text-gray-500" />
<span className="font-medium">Vergi No:</span>
<span className="font-medium">
{translate('::Public.payment.customer.taxNumber')}:
</span>
<span>{tenant.vknTckn}</span>
</div>
{tenant.reference && (
<div className="flex items-center gap-2">
<FaUserPlus className="w-4 h-4 text-gray-500" />
<span className="font-medium">Referans:</span>
<span className="font-medium">
{translate('::Public.payment.customer.reference')}:
</span>
<span>{tenant.reference}</span>
</div>
)}
@ -221,7 +252,8 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
{/* 1. Sütun: Ödeme Yöntemi */}
<div className="bg-white rounded-xl shadow border p-6">
<h2 className="text-lg font-semibold mb-4 flex items-center">
<FaLock className="w-5 h-5 text-green-600 mr-2" /> Ödeme Yöntemi
<FaLock className="w-5 h-5 text-green-600 mr-2" />{' '}
{translate('::Public.payment.method.title')}
</h2>
<div className="space-y-2">
{paymentMethods.map((method) => (
@ -245,7 +277,9 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
<div>
<div className="font-medium text-gray-900">{method.name}</div>
<div className="text-sm text-gray-600">
{method.id === 'credit-card' ? 'Taksit seçenekleri mevcut' : 'Komisyon yok'}
{method.id === 'credit-card'
? translate('::Public.payment.method.installmentsAvailable')
: translate('::Public.payment.method.noCommission')}
</div>
</div>
</label>
@ -256,7 +290,9 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
{/* Taksit Seçenekleri */}
{selectedPaymentMethod === 'credit-card' && (
<div className="bg-white rounded-xl shadow border p-4">
<h3 className="text-md font-medium text-gray-800 mb-2">Taksit Seçenekleri</h3>
<h3 className="text-md font-medium text-gray-800 mb-2">
{translate('::Public.payment.installments.title')}
</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
{installmentOptions.map((option) => (
<label
@ -277,13 +313,15 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
/>
<div className="font-semibold text-base mb-1">{option.name}</div>
<div className="text-gray-500 mb-1">
Komisyon:{' '}
{translate('::Public.payment.installments.commission')}{' '}
<span className="font-semibold">
%{(option.commission * 100).toFixed(1)}
</span>
</div>
<div className="text-blue-700 font-bold mb-1">
{option.id > 1 ? `${option.id} x aylık` : 'Tek ödeme'}
{option.id > 1
? `${option.id} ${translate('::Public.payment.installments.monthly')}`
: translate('::Public.payment.installments.single')}
</div>
<div className="font-extrabold text-lg text-gray-900 mt-1">
{formatPrice(
@ -302,14 +340,16 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
{/* Kart Bilgileri */}
{selectedPaymentMethod !== 'bank-transfer' && (
<div className="bg-white rounded-xl shadow border p-6 space-y-3">
<h3 className="text-md font-medium text-gray-800 mb-3">Kart Bilgileri</h3>
<h3 className="text-md font-medium text-gray-800 mb-3">
{translate('::Public.payment.card.title')}
</h3>
<input
type="text"
required
value={paymentData.cardName}
onChange={(e) => handleInputChange('cardName', e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg"
placeholder="Kart Üzerindeki İsim"
placeholder={translate('::Public.payment.card.name')}
/>
<input
type="text"
@ -317,7 +357,7 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
value={paymentData.cardNumber}
onChange={(e) => handleInputChange('cardNumber', e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg"
placeholder="Kart Numarası"
placeholder={translate('::Public.payment.card.number')}
maxLength={19}
/>
<div className="grid grid-cols-2 gap-3">
@ -345,7 +385,9 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
{/* Sipariş Özeti ve Butonlar */}
<div className="bg-white rounded-xl shadow border p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Sipariş Özeti</h3>
<h3 className="text-lg font-semibold text-gray-900 mb-4">
{translate('::Public.payment.summary.title')}
</h3>
<div className="space-y-4 mb-4">
{cartState.items.map((item, index) => (
@ -370,18 +412,18 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
<div className="space-y-1 text-sm border-t border-gray-200 pt-4 text-gray-600">
<div className="flex justify-between">
<span>Ara Toplam:</span>
<span>{translate('::Public.payment.summary.subtotal')}</span>
<span>{formatPrice(cartState.total)}</span>
</div>
<div className="flex justify-between">
<span>Komisyon:</span>
<span>{translate('::Public.payment.summary.commission')}</span>
<span>{formatPrice(commission)}</span>
</div>
{selectedPaymentMethod === 'credit-card' &&
selectedInstallment?.id &&
selectedInstallment.id > 1 && (
<div className="flex justify-between text-blue-600">
<span>Aylık Taksit:</span>
<span>{translate('::Public.payment.summary.monthlyInstallment')}: </span>
<span>
{formatPrice(finalTotal / selectedInstallment.id)} x{' '}
{selectedInstallment.id}
@ -389,7 +431,7 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
</div>
)}
<div className="flex justify-between text-base font-bold pt-2 text-gray-900">
<span>Toplam:</span>
<span>{translate('::Public.payment.summary.total')}</span>
<span className="text-blue-600">{formatPrice(finalTotal)}</span>
</div>
</div>
@ -402,14 +444,16 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({
className="flex items-center px-6 py-3 border border-gray-300 text-gray-700 rounded-lg"
>
<FaArrowLeft className="w-4 h-4 mr-2" />
Geri
{translate('::Public.payment.buttons.back')}
</button>
<button
type="submit"
className="flex items-center justify-center px-6 py-3 bg-green-600 text-white rounded-lg"
>
<FaCreditCard className="w-5 h-5 mr-2" />
{selectedPaymentMethod === 'bank-transfer' ? 'Siparişi Tamamla' : 'Ödeme Yap'}
{selectedPaymentMethod === 'bank-transfer'
? translate('::Public.payment.buttons.completeOrder')
: translate('::Public.payment.buttons.pay')}
</button>
</div>
</div>

View file

@ -1,14 +1,15 @@
import React, { useState } from 'react';
import { FaPlus, FaMinus } from 'react-icons/fa';
import { BillingCycle, Product, ProductDto } from '@/proxy/order/models';
import { CartState } from '@/utils/cartUtils';
import React, { useState } from 'react'
import { FaPlus, FaMinus } from 'react-icons/fa'
import { BillingCycle, Product, ProductDto } from '@/proxy/order/models'
import { CartState } from '@/utils/cartUtils'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface ProductCardProps {
product: ProductDto;
globalBillingCycle: BillingCycle;
globalPeriod: number;
addItem: (product: ProductDto, quantity: number, billingCycle: BillingCycle) => void;
cartState: CartState;
product: ProductDto
globalBillingCycle: BillingCycle
globalPeriod: number
addItem: (product: ProductDto, quantity: number, billingCycle: BillingCycle) => void
cartState: CartState
}
export const ProductCard: React.FC<ProductCardProps> = ({
@ -16,66 +17,75 @@ export const ProductCard: React.FC<ProductCardProps> = ({
globalBillingCycle,
globalPeriod,
addItem,
cartState
cartState,
}) => {
const [quantity, setQuantity] = useState(1);
const [quantity, setQuantity] = useState(1)
const { translate } = useLocalization()
// Direct check with current cart state
const isInCart = cartState.items.some(
item => item.product.id === product.id && item.billingCycle === globalBillingCycle
);
(item) => item.product.id === product.id && item.billingCycle === globalBillingCycle,
)
const formatPrice = (price: number) => {
return new Intl.NumberFormat('tr-TR', {
style: 'currency',
currency: 'TRY',
minimumFractionDigits: 0
}).format(price);
};
minimumFractionDigits: 0,
}).format(price)
}
const getCurrentPrice = () => {
return globalBillingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!;
};
return globalBillingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!
}
const getTotalPrice = () => {
return getCurrentPrice() * quantity * globalPeriod;
};
return getCurrentPrice() * quantity * globalPeriod
}
const handleAddToCart = () => {
// Non-quantity products should not be added if already in cart
if (!product.isQuantityBased && isInCart) {
return;
return
}
// For non-quantity products, always use quantity 1
const quantityToAdd = !product.isQuantityBased ? 1 : quantity;
addItem(product, quantityToAdd, globalBillingCycle);
setQuantity(1);
};
const quantityToAdd = !product.isQuantityBased ? 1 : quantity
addItem(product, quantityToAdd, globalBillingCycle)
setQuantity(1)
}
const getUnitText = () => {
switch (product.unit) {
case 'monthly': return globalBillingCycle === 'monthly' ? '/ Ay' : '/ Yıl';
case 'yearly': return '/ Yıl';
default: return '';
case 'monthly':
return globalBillingCycle === 'monthly'
? translate('::Public.products.price.perMonthSuffix')
: translate('::Public.products.price.perYearSuffix')
case 'yearly':
return translate('::Public.products.price.perYearSuffix')
default:
return ''
}
};
}
const getPeriodText = () => {
const periodText = globalBillingCycle === 'monthly' ? 'Ay' : 'Yıl';
return globalPeriod > 1 ? ` (${globalPeriod} ${periodText})` : '';
};
const periodText =
globalBillingCycle === 'monthly'
? translate('::Public.products.period.month')
: translate('::Public.products.period.year')
return globalPeriod > 1 ? ` (${globalPeriod} ${periodText})` : ''
}
// Check if product has valid price for current billing cycle
const hasValidPrice = () => {
if (globalBillingCycle === 'monthly' && !product.monthlyPrice) return false;
if (globalBillingCycle === 'yearly' && !product.yearlyPrice) return false;
return true;
};
if (globalBillingCycle === 'monthly' && !product.monthlyPrice) return false
if (globalBillingCycle === 'yearly' && !product.yearlyPrice) return false
return true
}
// If product doesn't have valid price, don't render it at all
if (!hasValidPrice()) {
return null;
return null
}
return (
@ -93,23 +103,21 @@ export const ProductCard: React.FC<ProductCardProps> = ({
<div className="p-6 flex flex-col flex-1">
<div className="mb-2">
<span className="inline-block px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full">
{product.category}
{translate('::' + product.category)}
</span>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2 line-clamp-2">
{product.name}
</h3>
<h3 className="text-lg font-semibold text-gray-900 mb-2 line-clamp-2">{translate('::' + product.name)}</h3>
<p className="text-gray-600 text-sm mb-4 line-clamp-3">
{product.description}
</p>
<p className="text-gray-600 text-sm mb-4 line-clamp-3">{translate('::' + product.description)}</p>
{/* Quantity and Yearly Savings above price */}
<div className="mb-4 space-y-3">
{product.isQuantityBased && (
<div className="flex items-center space-x-3">
<span className="text-sm font-medium text-gray-700">Adet:</span>
<span className="text-sm font-medium text-gray-700">
{translate('::Public.products.quantity')}
</span>
<div className="flex items-center space-x-2">
<button
onClick={() => setQuantity(Math.max(1, quantity - 1))}
@ -130,7 +138,9 @@ export const ProductCard: React.FC<ProductCardProps> = ({
{globalBillingCycle === 'yearly' && product.monthlyPrice && product.yearlyPrice && (
<div className="text-sm text-emerald-600 font-medium">
Yıllık ile %{Math.round((1 - (product.yearlyPrice / (product.monthlyPrice * 12))) * 100)} tasarruf
{translate('::Public.products.savings.yearly', {
percent: Math.round((1 - product.yearlyPrice / (product.monthlyPrice * 12)) * 100),
})}
</div>
)}
</div>
@ -140,23 +150,19 @@ export const ProductCard: React.FC<ProductCardProps> = ({
<div className="mb-4">
<div className="text-2xl font-bold text-gray-900">
{formatPrice(getCurrentPrice())}
<span className="text-sm font-normal text-gray-500 ml-1">
{getUnitText()}
</span>
<span className="text-sm font-normal text-gray-500 ml-1">{getUnitText()}</span>
</div>
{globalPeriod > 1 && (
<div className="text-lg font-semibold text-blue-600 mt-1">
Toplam: {formatPrice(getTotalPrice())}
<span className="text-sm font-normal text-gray-500 ml-1">
{getPeriodText()}
</span>
{translate('::Public.products.total')} {formatPrice(getTotalPrice())}
<span className="text-sm font-normal text-gray-500 ml-1">{getPeriodText()}</span>
</div>
)}
</div>
{(() => {
const isNonQuantityProduct = !product.isQuantityBased;
const isDisabled = isNonQuantityProduct && isInCart;
const isNonQuantityProduct = !product.isQuantityBased
const isDisabled = isNonQuantityProduct && isInCart
return (
<button
@ -168,12 +174,12 @@ export const ProductCard: React.FC<ProductCardProps> = ({
: 'bg-blue-600 hover:bg-blue-700 text-white hover:scale-[1.02] active:scale-[0.98]'
}`}
>
{isDisabled ? 'Sepette Mevcut' : 'Sepete Ekle'}
{isDisabled ? translate('::Public.products.inCart') : translate('::Public.products.addToCart')}
</button>
);
)
})()}
</div>
</div>
</div>
);
};
)
}

View file

@ -4,6 +4,7 @@ import { ProductCard } from './ProductCard'
import { addItemToCart, CartState } from '@/utils/cartUtils'
import { BillingCycle, ProductDto } from '@/proxy/order/models'
import { OrderService } from '@/services/order.service'
import { useLocalization } from '@/utils/hooks/useLocalization';
interface ProductCatalogProps {
searchQuery: string
@ -18,6 +19,8 @@ export const ProductCatalog: React.FC<ProductCatalogProps> = ({
cartState,
setCartState,
}) => {
const { translate } = useLocalization()
const [selectedCategory, setSelectedCategory] = useState<string>('all')
const [products, setProducts] = useState<ProductDto[]>([])
const [loading, setLoading] = useState<boolean>(true)
@ -87,7 +90,7 @@ export const ProductCatalog: React.FC<ProductCatalogProps> = ({
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 sticky top-40">
<div className="flex items-center space-x-2 mb-4">
<FaFilter className="w-5 h-5 text-gray-600" />
<h3 className="font-semibold text-gray-900">Kategoriler</h3>
<h3 className="font-semibold text-gray-900">{translate('::Public.products.categories')}</h3>
</div>
<div className="space-y-2">
{categories.map((category) => (
@ -100,7 +103,7 @@ export const ProductCatalog: React.FC<ProductCatalogProps> = ({
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
}`}
>
<span>{category === 'all' ? 'Tüm Ürünler' : category}</span>
<span>{category === 'all' ? translate('::Public.products.categories.all') : translate('::' + category)}</span>
<span
className={`text-xs px-2 py-1 rounded-full ${
selectedCategory === category
@ -118,13 +121,13 @@ export const ProductCatalog: React.FC<ProductCatalogProps> = ({
<div className="mt-6">
<div className="flex items-center space-x-2 mb-3">
<FaSearch className="w-5 h-5 text-gray-600" />
<h3 className="font-semibold text-gray-900">Arama</h3>
<h3 className="font-semibold text-gray-900">{translate('::Public.products.search.title')}</h3>
</div>
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<input
type="text"
placeholder="Ürün ara..."
placeholder={translate('::Public.products.search.placeholder')}
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
@ -136,7 +139,7 @@ export const ProductCatalog: React.FC<ProductCatalogProps> = ({
<main className="flex-1">
{loading ? (
<div className="text-center py-12 text-gray-500">Yükleniyor...</div>
<div className="text-center py-12 text-gray-500">{translate('::Public.products.loading')}</div>
) : (
<>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
@ -156,8 +159,8 @@ export const ProductCatalog: React.FC<ProductCatalogProps> = ({
<div className="text-gray-400 mb-2">
<FaFilter className="w-12 h-12 mx-auto" />
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Ürün bulunamadı</h3>
<p className="text-gray-600">Arama kriterlerinizi değiştirmeyi deneyin.</p>
<h3 className="text-lg font-medium text-gray-900 mb-2">{translate('::Public.products.empty.title')}</h3>
<p className="text-gray-600">{translate('::Public.products.empty.description')}</p>
</div>
)}
</>

View file

@ -17,6 +17,7 @@ import {
} from 'react-icons/fa'
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface TenantFormProps {
onSubmit: (tenant: CustomTenantDto) => void
@ -27,6 +28,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
const [isExisting, setIsExisting] = useState<boolean>(true)
const [formData, setFormData] = useState<Partial<CustomTenantDto>>({})
const navigate = useNavigate()
const { translate } = useLocalization()
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
@ -73,7 +75,8 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
<div className="w-full lg:w-1/3 bg-white rounded-xl shadow-lg border border-gray-200 p-6">
<div className="mb-6">
<h2 className="text-lg font-semibold mb-4 flex items-center">
<FaUser className="w-5 h-5 text-green-600 mr-2" /> Müşteri Bilgileri
<FaUser className="w-5 h-5 text-green-600 mr-2" />{' '}
{translate('::Public.products.tenantForm.customerInfo')}
</h2>
</div>
@ -87,8 +90,12 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
: 'border-gray-200 hover:border-blue-300'
}`}
>
<div className="font-semibold text-gray-900 mb-2">Mevcut Müşteri</div>
<div className="text-sm text-gray-600">Kurum kodunuz ile giriş yapın</div>
<div className="font-semibold text-gray-900 mb-2">
{translate('::Public.products.tenantForm.existing.title')}
</div>
<div className="text-sm text-gray-600">
{translate('::Public.products.tenantForm.existing.desc')}
</div>
</button>
<button
@ -99,8 +106,12 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
: 'border-gray-200 hover:border-blue-300'
}`}
>
<div className="font-semibold text-gray-900 mb-2">Yeni Müşteri</div>
<div className="text-sm text-gray-600">Kayıt olun ve hemen başlayın</div>
<div className="font-semibold text-gray-900 mb-2">
{translate('::Public.products.tenantForm.new.title')}
</div>
<div className="text-sm text-gray-600">
{translate('::Public.products.tenantForm.new.desc')}
</div>
</button>
</div>
</div>
@ -111,7 +122,9 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
<form onSubmit={handleSubmit} className="space-y-6">
{isExisting ? (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Kurum Kodu *</label>
<label className="block text-sm font-medium text-gray-700 mb-2">
{translate('::Public.products.tenantForm.orgCode')}
</label>
<div className="relative flex flex-col sm:flex-row sm:items-stretch">
<div className="relative w-full">
<FaBuilding className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
@ -134,10 +147,10 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
<button
type="button"
onClick={getTenantInfo}
className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-600 text-white rounded-lg sm:rounded-r-lg sm:rounded-l-none hover:bg-blue-700 transition-colors min-w-[148px]"
className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-600 text-white rounded-lg sm:rounded-r-lg sm:rounded-l-none hover:bg-blue-700 transition-colors min-w-[180px]"
>
<FaSearch className="w-4 h-4" />
Kurumu Bul
{translate('::Public.products.tenantForm.searchOrg')}
</button>
</div>
@ -145,76 +158,100 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
<div className="grid grid-cols-1 gap-y-3 text-sm text-gray-700 p-3">
<div className="flex items-center gap-2">
<FaUser className="w-4 h-4 text-gray-500" />
<span className="font-medium">Kurucu:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.founder')}
</span>
<span>{formData.founder}</span>
</div>
<div className="flex items-center gap-2">
<FaBuilding className="w-4 h-4 text-gray-500" />
<span className="font-medium">Şirket:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.company')}
</span>
<span>{formData.institutionName}</span>
</div>
<div className="flex items-center gap-2">
<FaEnvelope className="w-4 h-4 text-gray-500" />
<span className="font-medium">E-posta:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.email')}:
</span>
<span>{formData.email}</span>
</div>
<div className="flex items-center gap-2">
<FaPhone className="w-4 h-4 text-gray-500" />
<span className="font-medium">Mobile:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.mobile')}:
</span>
<span>{formData.mobile}</span>
</div>
<div className="flex items-start gap-2">
<FaMapPin className="w-4 h-4 text-gray-500 mt-0.5" />
<div>
<span className="font-medium">Adres:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.address')}:
</span>
<div>{formData.address}</div>
</div>
</div>
<div className="flex items-center gap-2">
<FaGlobe className="w-4 h-4 text-gray-500" />
<span className="font-medium">Ülke:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.country')}:
</span>
<span>{formData.country}</span>
</div>
<div className="flex items-center gap-2">
<FaGlobe className="w-4 h-4 text-gray-500" />
<span className="font-medium">Şehir:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.city')}:
</span>
<span>{formData.city}</span>
</div>
<div className="flex items-center gap-2">
<FaMap className="w-4 h-4 text-gray-500" />
<span className="font-medium">İlçe:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.district')}:
</span>
<span>{formData.district}</span>
</div>
<div className="flex items-center gap-2">
<FaMapPin className="w-4 h-4 text-gray-500" />
<span className="font-medium">Posta Kodu:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.postalCode')}:
</span>
<span>{formData.postalCode}</span>
</div>
<div className="flex items-center gap-2">
<FaMoneyBillWave className="w-4 h-4 text-gray-500" />
<span className="font-medium">Vergi Dairesi:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.taxOffice')}:
</span>
<span>{formData.taxOffice}</span>
</div>
<div className="flex items-center gap-2">
<FaDollarSign className="w-4 h-4 text-gray-500" />
<span className="font-medium">Vergi No:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.taxNumber')}:
</span>
<span>{formData.vknTckn}</span>
</div>
{formData.reference && (
<div className="flex items-center gap-2">
<FaUserPlus className="w-4 h-4 text-gray-500" />
<span className="font-medium">Referans:</span>
<span className="font-medium">
{translate('::Public.products.tenantForm.reference')}:
</span>
<span>{formData.reference}</span>
</div>
)}
@ -226,7 +263,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Şirket Adı *
{translate('::Public.products.tenantForm.orgName')}
</label>
<div className="relative">
<FaBuilding className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
@ -242,7 +279,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
</div>
</div>
<div>
<label className="text-sm font-medium text-gray-700">Kurucu *</label>
<label className="text-sm font-medium text-gray-700">{translate('::Public.products.tenantForm.founder')}</label>
<div className="relative">
<FaUser className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
@ -259,7 +296,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-gray-700">Telefon *</label>
<label className="text-sm font-medium text-gray-700">{translate('::Public.products.tenantForm.phone')}</label>
<div className="relative">
<FaPhone className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
@ -273,7 +310,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
</div>
</div>
<div>
<label className="text-sm font-medium text-gray-700">E-posta *</label>
<label className="text-sm font-medium text-gray-700">{translate('::Public.products.tenantForm.email')}</label>
<div className="relative">
<FaEnvelope className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
@ -289,7 +326,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
</div>
<div>
<label className="text-sm font-medium text-gray-700">Adres *</label>
<label className="text-sm font-medium text-gray-700">{translate('::Public.products.tenantForm.address')}</label>
<div className="relative">
<FaMapPin className="absolute left-3 top-3 text-gray-400 w-5 h-5" />
<textarea
@ -305,7 +342,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-gray-700">Ülke</label>
<label className="text-sm font-medium text-gray-700">{translate('::Public.products.tenantForm.country')}</label>
<div className="relative">
<FaGlobe className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
@ -318,7 +355,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
</div>
</div>
<div>
<label className="text-sm font-medium text-gray-700">Şehir *</label>
<label className="text-sm font-medium text-gray-700">{translate('::Public.products.tenantForm.city')}</label>
<div className="relative">
<FaGlobe className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
@ -335,7 +372,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-gray-700">İlçe *</label>
<label className="text-sm font-medium text-gray-700">{translate('::Public.products.tenantForm.district')}</label>
<div className="relative">
<FaMapPin className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
@ -349,7 +386,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
</div>
</div>
<div>
<label className="text-sm font-medium text-gray-700">Posta Kodu *</label>
<label className="text-sm font-medium text-gray-700">{translate('::Public.products.tenantForm.postalCode')}</label>
<div className="relative">
<FaMapPin className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
@ -366,7 +403,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-gray-700">Vergi Dairesi *</label>
<label className="text-sm font-medium text-gray-700">{translate('::Public.products.tenantForm.taxOffice')}</label>
<div className="relative">
<FaBuilding className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
@ -380,7 +417,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
</div>
</div>
<div>
<label className="text-sm font-medium text-gray-700">Vergi No *</label>
<label className="text-sm font-medium text-gray-700">{translate('::Public.products.tenantForm.taxNumber')}</label>
<div className="relative">
<FaDollarSign className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
@ -396,12 +433,12 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
</div>
<div>
<label className="text-sm font-medium text-gray-700">Referans</label>
<label className="text-sm font-medium text-gray-700">{translate('::Public.products.tenantForm.reference')}</label>
<div className="relative">
<FaUserPlus className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="Referans kişi veya kodu"
placeholder={translate('::Public.products.tenantForm.reference')}
value={formData.reference || ''}
onChange={(e) => handleInputChange('reference', e.target.value)}
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
@ -418,14 +455,14 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
className="flex items-center px-6 py-3 border border-gray-300 text-gray-700 rounded-lg"
>
<FaArrowLeft className="w-4 h-4 mr-2" />
Geri
{translate('::Public.products.tenantForm.buttons.back')}
</button>
<button
type="submit"
className="flex items-center justify-center px-6 py-3 bg-green-600 text-white rounded-lg"
>
<FaArrowRight className="w-5 h-5 mr-2" />
Devam Et
{translate('::Public.products.tenantForm.buttons.continue')}
</button>
</div>
</form>

View file

@ -62,28 +62,26 @@ const _LanguageSelector = ({ className }: CommonProps) => {
}
return (
<Tooltip title={translate('::App.Languages.Language')}>
<Dropdown renderTitle={selectedLanguage} placement="bottom-end">
{languageList?.map((lang) => (
<Dropdown.Item
key={lang.cultureName}
className="justify-between"
eventKey={lang.cultureName}
onClick={() => onLanguageSelect(lang.cultureName)}
>
<span className="flex items-center">
<Avatar
size={18}
shape="circle"
src={`/img/countries/${lang.cultureName ?? 'default'}.png`}
/>
<span className="ltr:ml-2 rtl:mr-2">{lang.displayName}</span>
</span>
{currentLang === lang.cultureName && <FaCheck className="text-emerald-500 text-lg" />}
</Dropdown.Item>
))}
</Dropdown>
</Tooltip>
<Dropdown renderTitle={selectedLanguage} placement="bottom-end">
{languageList?.map((lang) => (
<Dropdown.Item
key={lang.cultureName}
className="justify-between"
eventKey={lang.cultureName}
onClick={() => onLanguageSelect(lang.cultureName)}
>
<span className="flex items-center">
<Avatar
size={18}
shape="circle"
src={`/img/countries/${lang.cultureName ?? 'default'}.png`}
/>
<span className="ltr:ml-2 rtl:mr-2">{lang.displayName}</span>
</span>
{currentLang === lang.cultureName && <FaCheck className="text-emerald-500 text-lg" />}
</Dropdown.Item>
))}
</Dropdown>
)
}

View file

@ -1,10 +1,25 @@
const getBaseResources = (state, resourceNames) => {
// Type definitions
interface LocalizationResource {
texts: Record<string, string>
baseResources: string[]
}
interface LocalizationState {
resources: Record<string, LocalizationResource>
}
interface LocalizationKey {
key: string
defaultValue?: string
}
const getBaseResources = (state: LocalizationState, resourceNames: string[]): Record<string, string> => {
if (!resourceNames.length) {
return {}
}
// [{}, {}, {}]
const texts = resourceNames.map((resourceName) => {
const texts = resourceNames.map((resourceName: string) => {
const resource = state.resources[resourceName]
return {
...resource.texts,
@ -15,8 +30,8 @@ const getBaseResources = (state, resourceNames) => {
return Object.assign({}, ...texts)
}
export const setLocalization = (state) => {
const values = {}
export const setLocalization = (state: LocalizationState): Record<string, Record<string, string>> => {
const values: Record<string, Record<string, string>> = {}
Object.entries(state.resources).forEach(([key, value]) => {
values[key] = {
...value.texts,
@ -27,23 +42,40 @@ export const setLocalization = (state) => {
return values
}
export const getLocalization = (texts, defaultResourceName, key, ...interpolateParams) => {
if (!key) key = ''
// 🔑 Interpolation helper
const interpolate = (text: string, params: Record<string, string | number>) => {
if (!params || typeof params !== 'object') return text
return Object.entries(params).reduce((acc, [key, value]) => {
const pattern = new RegExp(`{{\\s*${key}\\s*}}`, 'g')
return acc.replace(pattern, String(value))
}, text)
}
export const getLocalization = (
texts: Record<string, Record<string, string>>,
defaultResourceName: string | undefined,
key: string | LocalizationKey,
params?: Record<string, string | number>
): string => {
let keyString = ''
let defaultValue = ''
if (typeof key !== 'string') {
defaultValue = key.defaultValue
key = key.key
if (typeof key === 'string') {
keyString = key || ''
} else {
defaultValue = key.defaultValue || ''
keyString = key.key
}
const keys = key.split('::')
const warn = (message) => {
const keys = keyString.split('::')
const warn = (message: string) => {
if (import.meta.env.DEV) console.warn(message)
}
if (keys.length < 2) {
warn('The localization source separator (::) not found.')
return defaultValue || key
return defaultValue || keyString
}
if (!texts) return defaultValue || keys[1]
@ -56,7 +88,6 @@ export const getLocalization = (texts, defaultResourceName, key, ...interpolateP
if (!sourceName) {
warn('Localization source name is not specified and the defaultResourceName was not defined!')
return defaultValue || sourceKey
}
@ -71,11 +102,12 @@ export const getLocalization = (texts, defaultResourceName, key, ...interpolateP
return defaultValue || sourceKey
}
//TODO: Interpolate
// interpolateParams = interpolateParams.filter((params) => params != null)
// if (localization) localization = interpolate(localization, interpolateParams)
if (typeof localization !== 'string') localization = ''
return localization || defaultValue || key
// ✅ Interpolation using the helper function
if (localization && params && typeof params === 'object') {
localization = interpolate(localization, params)
}
return localization || defaultValue || keyString
}

View file

@ -1,14 +1,26 @@
import { getLocalization } from '@/services/localization.service'
import { useStoreState } from '@/store'
type TranslateParams = Record<string, string | number>
const useLocalization = () => {
const { texts, config } = useStoreState((state) => state.abpConfig)
function translate(localizationKey: string, defaultResourceName?: string) {
function translate(
localizationKey: string,
params?: TranslateParams,
defaultResourceName?: string
): string {
// Guard against undefined texts
if (!texts) {
return localizationKey
}
return getLocalization(
texts,
defaultResourceName ?? config?.localization.defaultResourceName,
defaultResourceName ?? config?.localization?.defaultResourceName,
localizationKey,
params
)
}

View file

@ -216,12 +216,12 @@ function RolesPermission({
<div className="flex flex-col md:flex-row gap-4 mb-2">
<div style={{ width: '30%' }}>
<Checkbox name="all" checked={isAllSelected} onChange={onSelectAll}>
{translate('::SelectAllInAllTabs', 'AbpPermissionManagement')}
{translate('AbpPermissionManagement::SelectAllInAllTabs')}
</Checkbox>
</div>
<div style={{ width: '70%' }}>
<Checkbox name="group" checked={isAllSelectedForGroup} onChange={onSelectAll}>
{translate('::SelectAllInThisTab', 'AbpPermissionManagement')}
{translate('AbpPermissionManagement::SelectAllInThisTab')}
</Checkbox>
</div>
</div>

View file

@ -226,12 +226,12 @@ function UsersPermission({
<div className="flex flex-col md:flex-row gap-4">
<div style={{ width: '30%' }}>
<Checkbox name="all" checked={isAllSelected} onChange={onSelectAll}>
{translate('::SelectAllInAllTabs', 'AbpPermissionManagement')}
{translate('AbpPermissionManagement::SelectAllInAllTabs')}
</Checkbox>
</div>
<div style={{ width: '70%' }}>
<Checkbox name="group" checked={isAllSelectedForGroup} onChange={onSelectAll}>
{translate('::SelectAllInThisTab', 'AbpPermissionManagement')}
{translate('AbpPermissionManagement::SelectAllInThisTab')}
</Checkbox>
</div>
</div>

View file

@ -123,19 +123,19 @@ const Blog = () => {
<div className="flex gap-2 flex-wrap">
<button
onClick={() => handleCategoryChange('')}
className={`px-4 py-2 rounded-lg transition-colors ${
className={`px-4 py-2 bg-blue-100 text-blue-800 text-sm font-medium rounded-lg transition-colors ${
selectedCategory === ''
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
Tümü
{translate('::App.Reports.Dashboard.All')}
</button>
{categories.map((category) => (
<button
key={category.id}
onClick={() => handleCategoryChange(category.id)}
className={`px-4 py-2 rounded-lg transition-colors ${
className={`px-4 py-2 bg-blue-100 text-blue-800 text-sm font-medium rounded-lg transition-colors ${
selectedCategory === category.id
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'