Public sayfalar Custom componente taşındı
This commit is contained in:
parent
ad00e75911
commit
ea0de1e611
91 changed files with 1853 additions and 8079 deletions
119
README.md
119
README.md
|
|
@ -59,7 +59,7 @@ React/C# kodu yazmadan, yalnızca konfigürasyon ile mümkündür.
|
||||||
| **Arka plan işleri** | Hangfire + ABP Background Worker; zamanlanmış SQL, mail kuyruğu, yedekleme, oturum temizliği. |
|
| **Arka plan işleri** | Hangfire + ABP Background Worker; zamanlanmış SQL, mail kuyruğu, yedekleme, oturum temizliği. |
|
||||||
| **Gerçek zamanlı** | SignalR tabanlı messenger ve video oda (WebRTC/TURN) altyapısı. |
|
| **Gerçek zamanlı** | SignalR tabanlı messenger ve video oda (WebRTC/TURN) altyapısı. |
|
||||||
| **Intranet & Forum** | Duyuru, anket, sosyal duvar, etkinlik; kategori/konu/gönderi tabanlı forum. |
|
| **Intranet & Forum** | Duyuru, anket, sosyal duvar, etkinlik; kategori/konu/gönderi tabanlı forum. |
|
||||||
| **Public web sitesi** | Ana sayfa, hakkımızda, hizmetler, ürün, ödeme, blog, demo ve iletişim içerikleri (tasarımcı ekranlarıyla düzenlenebilir). |
|
| **Public web sitesi** | Menüsü ve sayfaları veritabanından yönetilen ziyaretçi sitesi: sayfalar Custom Component, menü `Pub` modülü (bkz. §10.4). Ürün/ödeme ve demo akışları fiziksel sayfalardır. |
|
||||||
| **PWA** | Service worker ile offline kabuk, kontrollü sürüm güncellemesi ve changelog bildirimi. |
|
| **PWA** | Service worker ile offline kabuk, kontrollü sürüm güncellemesi ve changelog bildirimi. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -845,6 +845,21 @@ parametresi (`?RoleId=…`), diğerleri son ek taşır (`?Name.contains=…`). O
|
||||||
(`designerScriptDialect.ts` + `designerScriptRecipes.ts`); tarif grupları bileşen erişimi, API
|
(`designerScriptDialect.ts` + `designerScriptRecipes.ts`); tarif grupları bileşen erişimi, API
|
||||||
çağrıları, form ve olay/sayfa başlıkları altında toplanır.
|
çağrıları, form ve olay/sayfa başlıkları altında toplanır.
|
||||||
|
|
||||||
|
**Kanvas doğruluğu.** Tasarım zamanında görülen, canlıda görülendir; bunu iki mekanizma sağlar.
|
||||||
|
(1) *Yerleşim sınıflarının taşınması*: kanvas her düğümü seçim/sürükleme için bir sarmalayıcıya
|
||||||
|
alır ve üst öğe grid/flex olduğunda asıl öğe o sarmalayıcı olur. `splitLayoutClasses` düğümün
|
||||||
|
yerleşim sınıflarını sarmalayıcıya geçirir — `col-span`/`row-span`/`order`/`self`/`flex-1`/`basis`,
|
||||||
|
sabit ve kesirli `w-*`, kenar boşlukları taşınır; konum sınıfları (`absolute`, `inset-*`, `z-*`,
|
||||||
|
`w-full`) her ikisinde de kalır. Görünürlük sınıfları da sarmalayıcıya kopyalanır (`hidden lg:flex`
|
||||||
|
→ sarmalayıcıda `hidden lg:block`): aksi hâlde mobilde gizlenmesi gereken masaüstü şeridi yine bir
|
||||||
|
grid hücresi işgal eder ve komşularını yanlış hücreye iter. `fixed` kanvasta akışa çevrilir, yoksa
|
||||||
|
düğüm tasarım yüzeyinden taşardı. (2) *Cihaz çerçevesi*: araç çubuğundan masaüstü/tablet/mobil seçildiğinde kanvas kendi
|
||||||
|
belgesinde çizilir (`CanvasFrame`), böylece `md:`/`lg:` kuralları tarayıcı penceresine değil seçilen
|
||||||
|
genişliğe göre çözülür. Cihaz modunda tasarım yüzeyi kenar boşluğunu bırakır: kanvas o cihazın ekranıdır. `Responsive`
|
||||||
|
seçimi aynı sayfada, çerçevesiz ve boşluklu çizer. (3) *Gezinme kapalıdır*: kanvastaki bir bağlantıya
|
||||||
|
tıklamak seçim yapar, sayfayı değiştirmez — tıklama yakalama evresinde durdurulur, `react-router`'ın
|
||||||
|
`Link`'i de `defaultPrevented` görünce gezinmez.
|
||||||
|
|
||||||
**Diğer notlar.** `PlatformIcon` tasarımcı dokümanındaki ikon adını çözer; `selectComponents.ts`
|
**Diğer notlar.** `PlatformIcon` tasarımcı dokümanındaki ikon adını çözer; `selectComponents.ts`
|
||||||
`Select.componentAs` için saklanan adı (`ReactSelect`, `CreatableSelect`, `AsyncSelect`) gerçek
|
`Select.componentAs` için saklanan adı (`ReactSelect`, `CreatableSelect`, `AsyncSelect`) gerçek
|
||||||
bileşene çevirir — kanvas ve üretilen bileşenin aynı adı aynı şekilde yorumlamasını sağlayan tek
|
bileşene çevirir — kanvas ve üretilen bileşenin aynı adı aynı şekilde yorumlamasını sağlayan tek
|
||||||
|
|
@ -875,6 +890,9 @@ Route.Authority ──┘ └► Permission
|
||||||
- Frontend'de `dynamicRoutesContext` route'ları çeker, `dynamicRouteLoader` bunları fiziksel
|
- Frontend'de `dynamicRoutesContext` route'ları çeker, `dynamicRouteLoader` bunları fiziksel
|
||||||
view'lara veya runtime derlenmiş Custom Component'lere eşler, `DynamicRouter` React Router
|
view'lara veya runtime derlenmiş Custom Component'lere eşler, `DynamicRouter` React Router
|
||||||
ağacını kurar; her korumalı route `ProtectedRoute` + `PermissionGuard` ile sarılır.
|
ağacını kurar; her korumalı route `ProtectedRoute` + `PermissionGuard` ile sarılır.
|
||||||
|
- Aynı yolu bir fiziksel route ile bir Custom Component paylaşıyorsa **Custom Component kazanır**;
|
||||||
|
karşılığı olan dosyası bulunmayan rota kaydı hiç yüklenmez. Public site menüsü bu modelin
|
||||||
|
dışındadır: `Pub` modülü altında tutulur ve uygulama konfigürasyonuyla taşınır (bkz. §10.4).
|
||||||
|
|
||||||
**Yetki kodları** `PlatformConsts.AppCodes` içinde merkezî olarak tanımlıdır ve
|
**Yetki kodları** `PlatformConsts.AppCodes` içinde merkezî olarak tanımlıdır ve
|
||||||
`PermissionsData.json` ile seed edilir. Ana gruplar: `App.Saas`, `App.Branches`, `App.Intranet.*`,
|
`PermissionsData.json` ile seed edilir. Ana gruplar: `App.Saas`, `App.Branches`, `App.Intranet.*`,
|
||||||
|
|
@ -895,7 +913,6 @@ içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppSer
|
||||||
| `App.DeveloperKit.DynamicServices` | `.Create`, `.Edit`, `.Delete`, `.Manage`, `.TestCompile`, `.Publish`, `.ViewCode` |
|
| `App.DeveloperKit.DynamicServices` | `.Create`, `.Edit`, `.Delete`, `.Manage`, `.TestCompile`, `.Publish`, `.ViewCode` |
|
||||||
| `App.SqlQueryManager` | `.CrudEndpoints` |
|
| `App.SqlQueryManager` | `.CrudEndpoints` |
|
||||||
| `App.Setup.Migrate` | Migration + seed tetikleme (host tarafı) |
|
| `App.Setup.Migrate` | Migration + seed tetikleme (host tarafı) |
|
||||||
| `App.{Home,About,Services,Contact}.Design` | Public site sayfa tasarım modu (`?design=1`) |
|
|
||||||
|
|
||||||
> **Kural:** Yetki sözleşmesi olmayan menü/route önerilmez ve eklenmez.
|
> **Kural:** Yetki sözleşmesi olmayan menü/route önerilmez ve eklenmez.
|
||||||
|
|
||||||
|
|
@ -943,9 +960,101 @@ içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppSer
|
||||||
|
|
||||||
### 10.4. Public site
|
### 10.4. Public site
|
||||||
|
|
||||||
`/home` · `/about` · `/services` · `/products` · `/checkout` · `/payment` · `/success` ·
|
`/home` · `/about` · `/contact` (Custom Component) · `/products` · `/checkout` · `/payment` ·
|
||||||
`/blog` · `/blog/:id` · `/demo` · `/contact` — içerikler veritabanından yönetilir;
|
`/success` · `/blog` · `/blog/:id` · `/demo` (fiziksel sayfalar).
|
||||||
`/about/designer` ve `/services/designer` sayfa tasarımcılarıdır.
|
|
||||||
|
Ziyaretçiye açık kısım iki parçadan oluşur ve ikisi de veritabanında yaşar:
|
||||||
|
|
||||||
|
**Menü.** Public menüler `Menu` tablosunda `ModuleId = "Pub"` modülü altında tutulur ve Menü
|
||||||
|
Yönetimi ekranından girilir (sözleşme: `PlatformConsts.PublicSite`). `App.Public.Header` kökü hem
|
||||||
|
üst navigasyonu hem de alt bilgideki hızlı bağlantıları besler — ikisi de `PublicNav` düğümüdür,
|
||||||
|
menü bir kez eklenir iki yerde birden görünür; alt kaydı olan bir menü üstte açılır liste olur.
|
||||||
|
Alt bilgide `cta` kayıtları (Giriş) yer almaz: oradaki düğüm `source="navigation"` ile çizilir. `Url` sayfa yolu, `Icon`
|
||||||
|
react-icons adı, `Order` sıra, `IsDisabled` yayın anahtarı, `DisplayName` dil anahtarıdır;
|
||||||
|
`CssClass` içinde `cta` geçen kayıt düz bağlantı yerine vurgulu buton olarak çizilir. Menü ucu
|
||||||
|
`[Authorize]` olduğu için menüler uygulama konfigürasyonunun `publicMenus` alanıyla taşınır
|
||||||
|
(`PlatformApplicationConfigurationContributor`), istemcide `usePublicMenu` okur. `Pub` modülü
|
||||||
|
yönetim navigasyonuna girmez (`abpConfig.model.ts` menüyü kurarken bu modülü eler; Menü Yönetimi
|
||||||
|
ekranı elemez, menüler oradan düzenlenir). Seed: `MenusData.json` → `Modules` + `Menus`.
|
||||||
|
|
||||||
|
**Sayfa.** Her public sayfa bir Custom Component'tir: `RoutePath` `/admin/` ile başlamadığı sürece
|
||||||
|
bileşen otomatik olarak public route olur ve `PublicLayout` altında yayınlanır. Tasarım yalnızca
|
||||||
|
DeveloperKit > Custom Components (Visual Designer) üzerinden değişir. Aynı yolu bir fiziksel route
|
||||||
|
ile paylaşan custom component kazanır; karşılığı olan dosyası bulunmayan rota kaydı ise hiç
|
||||||
|
yüklenmez (`hasPhysicalComponent`) — seeder yalnızca eklediği için eski kayıtlar veritabanında
|
||||||
|
kalsa bile ekran patlamaz. Seed: `configs/seeds/host/data/App.DeveloperKit.CustomComponents.json`;
|
||||||
|
bileşenin ayrı dosyası yoktur, her bileşen bu dosyada bir satırdır.
|
||||||
|
|
||||||
|
Runtime kapsamı sınırlıdır: bileşen kodundaki `import` satırları derlemede temizlenir,
|
||||||
|
kullanılabilenler `React`, `ui` kit, `PlatformViewHost`, `apiService`, `axios`, `DOMPurify`,
|
||||||
|
`translate`, `checkPermission`, `getCulture` ve **katalog düğümleri**
|
||||||
|
(`components/componentEditor/runtimeNodes.ts`). **`react-icons` ve `react-router` yoktur** — ikon
|
||||||
|
`<PlatformIcon name="FaUsers" size={40} className="..." />` ile ada göre çizilir (menü ikonlarıyla
|
||||||
|
aynı sözlük), gezinme `<a href>` ile yazılır. Kullanıcıya görünen metinler dil anahtarı üzerinden
|
||||||
|
gelir.
|
||||||
|
|
||||||
|
**Tailwind sınıf yüzeyi.** Bileşenin kodu veritabanında yaşar; derleme sırasında taranan tek
|
||||||
|
kaynak seed dosyasıdır (`configs/seeds/*/data/App.DeveloperKit.CustomComponents.json`). Bu dosya
|
||||||
|
depo kökünde, `ui/` dışındadır — derleme bağlamı yalnızca `ui/` klasörü olduğunda (docker, ayrı
|
||||||
|
checkout) glob boş döner ve seed'e özgü sınıflar (`md:col-span-4`, `bg-gray-950/80`, `opacity-20`)
|
||||||
|
CSS'e hiç girmez: uygulama yerelde doğru, sunucuda stilsiz görünür. Bu yüzden aynı sınıflar var
|
||||||
|
olan `ui/safelist.txt` dosyasına da yazılır. Dosyayı zaten `twSafelistGenerator` eklentisi üretiyor
|
||||||
|
(renk/ölçü listesi); eklenti artık seed dosyalarındaki `className` değerlerini de toplayıp
|
||||||
|
`# --- custom component classes ---` işaretinin altına ekliyor. `safelist.txt` `ui/` içinde ve
|
||||||
|
depoda olduğu için her ortamda taranır. Seed kökü görünmüyorsa işaretin altındaki liste olduğu gibi
|
||||||
|
korunur — eksik bir bağlam listeyi silip sorunu büyütmez. Ayrı bir betik ya da ikinci bir safelist
|
||||||
|
dosyası yoktur; liste her `npm run build` / `npm start` ile tazelenir.
|
||||||
|
|
||||||
|
**Katalog düğümleri.** Kanvasın doğrudan çizdiği, derlenen bileşen listesinde yer almayan
|
||||||
|
bileşenler tek bir sözlükte durur (`RUNTIME_NODE_COMPONENTS`): kanvas onu `VisualCanvas` içinde,
|
||||||
|
üretilen kod ise runtime kapsamı üzerinden kullanır — iki taraf aynı kaynaktan beslendiği için
|
||||||
|
tasarım ve çalışma zamanı ayrışamaz. Sözlükte `PlatformIcon` ve public site parçaları
|
||||||
|
(`components/publicSite`) bulunur. Yeni bir düğüm eklerken: bileşen `components/publicSite` (ya da
|
||||||
|
uygun klasör) → `runtimeNodes.ts` sözlüğü → `catalog.ts` tanımı (`kind: 'custom'`) → `LanguagesData.json`
|
||||||
|
açıklama anahtarı.
|
||||||
|
|
||||||
|
Elle yazılmış bir bileşen Visual Designer'da **kod modunda** açılır: tasarımcı yalnızca kendi
|
||||||
|
ürettiği kodu düğüm ağacına çevirir (`isDesignerGeneratedCode`), aksi hâlde tanımadığı JSX'i
|
||||||
|
düşürüp kaydederken kodu yok ederdi.
|
||||||
|
|
||||||
|
Public bileşenlerin tamamı — `/home`, `/about`, `/contact` sayfaları ile üst ve alt bilgi
|
||||||
|
(`PublicHeader`, `PublicFooter`; rotaları yok) — **tasarımcı belgesi** olarak tutulur: `Props` alanındaki
|
||||||
|
`visualDesigner` düğüm ağacı kaynaktır (`sourceMode: "visual"`), `Code` ondan üretilir
|
||||||
|
(`generateDesignerCode`) ve runtime da bileşeni bu belgeden derler. Yani hepsi kanvasta görünür,
|
||||||
|
sürükle-bırak ve özellik paneliyle düzenlenir. Belgeler `div`/`h1`-`h5`/`p`/`span`/`img`/`a`
|
||||||
|
HTML düğümleri ve `PlatformIcon` ikon düğümüyle kurulmuştur; `::` ile başlayan metinler üretilen
|
||||||
|
kodda `translate()` çağrısına döner. `RoutePath` boş bırakılabilir; rotası olmayan bir bileşen
|
||||||
|
yalnızca başka bir ekranın içinde çizilir (alt bilgi gibi).
|
||||||
|
|
||||||
|
Üst ve alt bilgi de birer Custom Component'tir (`PublicHeader`, `PublicFooter`; rota yok):
|
||||||
|
`PublicLayout` ikisini de `renderComponent` ile çizer ve geriye yalnızca rota değişiminde başa
|
||||||
|
sarma ile demo penceresi kalır. Bileşen kayıtlı değilse o parça çizilmez.
|
||||||
|
|
||||||
|
Üst bilginin davranışı (menü listesi, aktif bağlantı, kaydırmada değişen görünüm, mobil panel,
|
||||||
|
tema/dil/demo kontrolleri) `components/publicSite` altındaki katalog düğümlerinde yaşar; yerleşim
|
||||||
|
ve sınıflar tasarımcıdadır: `PublicHeaderBar` (kaydırıldığında `scrolledClassName`'e geçen kabuk),
|
||||||
|
`PublicNav` (`source`: `navigation`/`actions`/`all`, `variant`: `horizontal`/`vertical`),
|
||||||
|
`PublicMobileMenu` (hamburger + içeriği tasarlanan panel), `PublicLogo`, `PublicThemeToggle`,
|
||||||
|
`PublicLanguageSelector`, `PublicDemoButton`.
|
||||||
|
|
||||||
|
Rotası olmayan bileşende `RoutePath` **NULL** yazılır; `Sas_H_CustomComponent`'in `RoutePath`
|
||||||
|
tekil indeksi yalnızca dolu değerleri kapsar (`[IsDeleted] = 0 AND [RoutePath] IS NOT NULL`), aksi
|
||||||
|
hâlde ikinci rotasız bileşen birincisiyle çakışırdı.
|
||||||
|
|
||||||
|
`/home`, `/about`, `/contact` bu yolla çalışır (`PublicHomePage`, `PublicAboutPage`,
|
||||||
|
`PublicContactPage`); üst ve alt bilgi rotasızdır (`PublicHeader`, `PublicFooter`).
|
||||||
|
|
||||||
|
Veriden beslenen public sayfalar (`/products`, `/checkout`, `/payment`, `/success`, `/blog`,
|
||||||
|
`/blog/:id`, `/demo`) fiziksel kalır; içerik sayfaları Custom Component'tir. Blog listesi ve detayı
|
||||||
|
`PublicAppService`'in `post-list` / `post-by-slug` uçlarından beslenir, yönetimi
|
||||||
|
`App.BlogManagement.*` ekranlarındadır.
|
||||||
|
|
||||||
|
Eski statik içerik sayfaları ve sayfa içi tasarım modu (`?design=1`) kaldırılmıştır:
|
||||||
|
`views/public/Home|About|Contact|Services`, `views/public/designer/`, bunlara özel istemci
|
||||||
|
servisleri; sunucuda `Home`/`About`/`Contact`/`Service` entity'leri, `PublicAppService` sayfa
|
||||||
|
Get/Save uçları ve DTO'ları, `TenantData.json` + `TenantDataSeeder`,
|
||||||
|
`App.{Home,About,Services,Contact}` yetkileri ve `.Design` alt yetkileri, `/admin/public/*/designer`
|
||||||
|
rota ve menüleri.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ namespace Sozsoft.Platform.DeveloperKit;
|
||||||
public class CustomComponentDto : FullAuditedEntityDto<Guid>
|
public class CustomComponentDto : FullAuditedEntityDto<Guid>
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public string RoutePath { get; set; } = string.Empty;
|
/// <summary>Rotasi olmayan bilesende (ust bilgi, alt bilgi) bostur.</summary>
|
||||||
|
public string? RoutePath { get; set; }
|
||||||
public string Code { get; set; } = string.Empty;
|
public string Code { get; set; } = string.Empty;
|
||||||
public string? Props { get; set; }
|
public string? Props { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
@ -24,10 +25,13 @@ public class CustomComponentDto : FullAuditedEntityDto<Guid>
|
||||||
public class CreateUpdateCustomComponentDto
|
public class CreateUpdateCustomComponentDto
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
[Required]
|
/// <summary>
|
||||||
|
/// Bilesenin kendi rotasi. Bos birakilabilir: rotasi olmayan bir bilesen (ornegin
|
||||||
|
/// baska bir ekranin icinde cizilen alt bilgi) hicbir adrese baglanmaz.
|
||||||
|
/// </summary>
|
||||||
[StringLength(512)]
|
[StringLength(512)]
|
||||||
[RegularExpression(@"^/.*", ErrorMessage = "RoutePath must start with '/'.")]
|
[RegularExpression(@"^(/.*)?$", ErrorMessage = "RoutePath must start with '/'.")]
|
||||||
public string RoutePath { get; set; } = string.Empty;
|
public string? RoutePath { get; set; }
|
||||||
public string Code { get; set; } = string.Empty;
|
public string Code { get; set; } = string.Empty;
|
||||||
public string? Props { get; set; }
|
public string? Props { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Volo.Abp.Application.Dtos;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Public;
|
|
||||||
|
|
||||||
public class 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); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Volo.Abp.Application.Dtos;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Public;
|
|
||||||
|
|
||||||
public class ContactDto : EntityDto<Guid>
|
|
||||||
{
|
|
||||||
public string Address { get; set; }
|
|
||||||
public string PhoneNumber { 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); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Volo.Abp.Application.Dtos;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Public;
|
|
||||||
|
|
||||||
public class HomeDto : EntityDto<Guid>
|
|
||||||
{
|
|
||||||
public string HeroBackgroundImageKey { get; set; }
|
|
||||||
public string HeroPrimaryCtaKey { get; set; }
|
|
||||||
public string HeroSecondaryCtaKey { get; set; }
|
|
||||||
public string FeaturesTitleKey { get; set; }
|
|
||||||
public string FeaturesSubtitleKey { get; set; }
|
|
||||||
public string SolutionsTitleKey { get; set; }
|
|
||||||
public string SolutionsSubtitleKey { get; set; }
|
|
||||||
public string CtaTitleKey { get; set; }
|
|
||||||
public string CtaSubtitleKey { get; set; }
|
|
||||||
public string CtaButtonLabelKey { get; set; }
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public string SlidesJson { get; set; }
|
|
||||||
|
|
||||||
public List<HomeSlideDto> SlidesDto
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(SlidesJson))
|
|
||||||
return JsonSerializer.Deserialize<List<HomeSlideDto>>(SlidesJson);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
set { SlidesJson = JsonSerializer.Serialize(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public string FeaturesJson { get; set; }
|
|
||||||
|
|
||||||
public List<HomeFeatureDto> FeaturesDto
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(FeaturesJson))
|
|
||||||
return JsonSerializer.Deserialize<List<HomeFeatureDto>>(FeaturesJson);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
set { FeaturesJson = JsonSerializer.Serialize(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public string SolutionsJson { get; set; }
|
|
||||||
|
|
||||||
public List<HomeSolutionDto> SolutionsDto
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(SolutionsJson))
|
|
||||||
return JsonSerializer.Deserialize<List<HomeSolutionDto>>(SolutionsJson);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
set { SolutionsJson = JsonSerializer.Serialize(value); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HomeSlideDto
|
|
||||||
{
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string SubtitleKey { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
public List<HomeSlideServiceDto> Services { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HomeSlideServiceDto
|
|
||||||
{
|
|
||||||
public string Icon { get; set; }
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string DescriptionKey { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HomeFeatureDto
|
|
||||||
{
|
|
||||||
public string Icon { get; set; }
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string DescriptionKey { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HomeSolutionDto
|
|
||||||
{
|
|
||||||
public string Icon { get; set; }
|
|
||||||
public string ColorClass { get; set; }
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string DescriptionKey { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,217 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Public;
|
|
||||||
|
|
||||||
public class SaveAboutPageInput
|
|
||||||
{
|
|
||||||
public string CultureName { get; set; }
|
|
||||||
public string HeroTitleKey { get; set; }
|
|
||||||
public string HeroTitleValue { get; set; }
|
|
||||||
public string HeroSubtitleKey { get; set; }
|
|
||||||
public string HeroSubtitleValue { get; set; }
|
|
||||||
public string HeroImageKey { get; set; }
|
|
||||||
public string HeroImageValue { get; set; }
|
|
||||||
public List<SaveAboutStatInput> Stats { get; set; } = [];
|
|
||||||
public List<SaveLocalizedTextInput> Descriptions { get; set; } = [];
|
|
||||||
public List<SaveAboutSectionInput> Sections { get; set; } = [];
|
|
||||||
public List<SaveLocalizedTextInput> StyleTexts { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaveAboutStatInput
|
|
||||||
{
|
|
||||||
public string Icon { get; set; }
|
|
||||||
public string Value { get; set; }
|
|
||||||
public string LabelKey { get; set; }
|
|
||||||
public string LabelValue { get; set; }
|
|
||||||
public bool? UseCounter { get; set; }
|
|
||||||
public string? CounterEnd { get; set; }
|
|
||||||
public string CounterSuffix { get; set; }
|
|
||||||
public int? CounterDuration { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaveAboutSectionInput
|
|
||||||
{
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string TitleValue { get; set; }
|
|
||||||
public string DescriptionKey { get; set; }
|
|
||||||
public string DescriptionValue { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaveServicesPageInput
|
|
||||||
{
|
|
||||||
public string CultureName { get; set; }
|
|
||||||
public string HeroTitleKey { get; set; }
|
|
||||||
public string HeroTitleValue { get; set; }
|
|
||||||
public string HeroSubtitleKey { get; set; }
|
|
||||||
public string HeroSubtitleValue { get; set; }
|
|
||||||
public string HeroImageKey { get; set; }
|
|
||||||
public string HeroImageValue { get; set; }
|
|
||||||
public string SupportTitleKey { get; set; }
|
|
||||||
public string SupportTitleValue { get; set; }
|
|
||||||
public string SupportButtonLabelKey { get; set; }
|
|
||||||
public string SupportButtonLabelValue { get; set; }
|
|
||||||
public string CtaTitleKey { get; set; }
|
|
||||||
public string CtaTitleValue { get; set; }
|
|
||||||
public string CtaDescriptionKey { get; set; }
|
|
||||||
public string CtaDescriptionValue { get; set; }
|
|
||||||
public string CtaButtonLabelKey { get; set; }
|
|
||||||
public string CtaButtonLabelValue { get; set; }
|
|
||||||
public List<SaveServiceItemInput> ServiceItems { get; set; } = [];
|
|
||||||
public List<SaveServiceItemInput> SupportItems { get; set; } = [];
|
|
||||||
public List<SaveLocalizedTextInput> StyleTexts { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaveServiceItemInput
|
|
||||||
{
|
|
||||||
public string? Icon { get; set; }
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string TitleValue { get; set; }
|
|
||||||
public string? DescriptionKey { get; set; }
|
|
||||||
public string? DescriptionValue { get; set; }
|
|
||||||
public string Type { get; set; }
|
|
||||||
public List<SaveLocalizedTextInput> Features { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaveLocalizedTextInput
|
|
||||||
{
|
|
||||||
public string Key { get; set; }
|
|
||||||
public string Value { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaveHomePageInput
|
|
||||||
{
|
|
||||||
public string CultureName { get; set; }
|
|
||||||
public string HeroBackgroundImageKey { get; set; }
|
|
||||||
public string HeroBackgroundImageValue { get; set; }
|
|
||||||
public string HeroPrimaryCtaKey { get; set; }
|
|
||||||
public string HeroPrimaryCtaValue { get; set; }
|
|
||||||
public string HeroPrimaryCtaStyleKey { get; set; }
|
|
||||||
public string HeroPrimaryCtaStyleValue { get; set; }
|
|
||||||
public string HeroSecondaryCtaKey { get; set; }
|
|
||||||
public string HeroSecondaryCtaValue { get; set; }
|
|
||||||
public string HeroSecondaryCtaStyleKey { get; set; }
|
|
||||||
public string HeroSecondaryCtaStyleValue { get; set; }
|
|
||||||
public string FeaturesTitleKey { get; set; }
|
|
||||||
public string FeaturesTitleValue { get; set; }
|
|
||||||
public string FeaturesTitleStyleKey { get; set; }
|
|
||||||
public string FeaturesTitleStyleValue { get; set; }
|
|
||||||
public string FeaturesSubtitleKey { get; set; }
|
|
||||||
public string FeaturesSubtitleValue { get; set; }
|
|
||||||
public string FeaturesSubtitleStyleKey { get; set; }
|
|
||||||
public string FeaturesSubtitleStyleValue { get; set; }
|
|
||||||
public string SolutionsTitleKey { get; set; }
|
|
||||||
public string SolutionsTitleValue { get; set; }
|
|
||||||
public string SolutionsTitleStyleKey { get; set; }
|
|
||||||
public string SolutionsTitleStyleValue { get; set; }
|
|
||||||
public string SolutionsSubtitleKey { get; set; }
|
|
||||||
public string SolutionsSubtitleValue { get; set; }
|
|
||||||
public string SolutionsSubtitleStyleKey { get; set; }
|
|
||||||
public string SolutionsSubtitleStyleValue { get; set; }
|
|
||||||
public string CtaTitleKey { get; set; }
|
|
||||||
public string CtaTitleValue { get; set; }
|
|
||||||
public string CtaTitleStyleKey { get; set; }
|
|
||||||
public string CtaTitleStyleValue { get; set; }
|
|
||||||
public string CtaSubtitleKey { get; set; }
|
|
||||||
public string CtaSubtitleValue { get; set; }
|
|
||||||
public string CtaSubtitleStyleKey { get; set; }
|
|
||||||
public string CtaSubtitleStyleValue { get; set; }
|
|
||||||
public string CtaButtonLabelKey { get; set; }
|
|
||||||
public string CtaButtonLabelValue { get; set; }
|
|
||||||
public string CtaButtonStyleKey { get; set; }
|
|
||||||
public string CtaButtonStyleValue { get; set; }
|
|
||||||
public List<SaveHomeSlideInput> Slides { get; set; } = [];
|
|
||||||
public List<SaveHomeFeatureInput> Features { get; set; } = [];
|
|
||||||
public List<SaveHomeSolutionInput> Solutions { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaveHomeSlideInput
|
|
||||||
{
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string TitleValue { get; set; }
|
|
||||||
public string SubtitleKey { get; set; }
|
|
||||||
public string SubtitleValue { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
public List<SaveHomeSlideServiceInput> Services { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaveHomeSlideServiceInput
|
|
||||||
{
|
|
||||||
public string Icon { get; set; }
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string TitleValue { get; set; }
|
|
||||||
public string DescriptionKey { get; set; }
|
|
||||||
public string DescriptionValue { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaveHomeFeatureInput
|
|
||||||
{
|
|
||||||
public string Icon { get; set; }
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string TitleValue { get; set; }
|
|
||||||
public string DescriptionKey { get; set; }
|
|
||||||
public string DescriptionValue { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaveHomeSolutionInput
|
|
||||||
{
|
|
||||||
public string Icon { get; set; }
|
|
||||||
public string ColorClass { get; set; }
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string TitleValue { get; set; }
|
|
||||||
public string DescriptionKey { get; set; }
|
|
||||||
public string DescriptionValue { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SaveContactPageInput
|
|
||||||
{
|
|
||||||
public string CultureName { get; set; }
|
|
||||||
|
|
||||||
public string HeroTitleKey { get; set; }
|
|
||||||
public string HeroTitleValue { get; set; }
|
|
||||||
public string HeroSubtitleKey { get; set; }
|
|
||||||
public string HeroSubtitleValue { get; set; }
|
|
||||||
public string HeroImageKey { get; set; }
|
|
||||||
public string HeroImageValue { get; set; }
|
|
||||||
|
|
||||||
public string ContactInfoTitleKey { get; set; }
|
|
||||||
public string ContactInfoTitleValue { get; set; }
|
|
||||||
public string AddressKey { get; set; }
|
|
||||||
public string AddressValue { get; set; }
|
|
||||||
public string PhoneNumber { get; set; }
|
|
||||||
public string Email { get; set; }
|
|
||||||
public string Location { get; set; }
|
|
||||||
public string TaxNumber { get; set; }
|
|
||||||
|
|
||||||
public string BankTitleKey { get; set; }
|
|
||||||
public string BankTitleValue { get; set; }
|
|
||||||
public string BankAccountHolder { get; set; }
|
|
||||||
public string BankBranch { get; set; }
|
|
||||||
public string BankAccountNumber { get; set; }
|
|
||||||
public string BankIban { get; set; }
|
|
||||||
public string BankStyleClass { get; set; }
|
|
||||||
|
|
||||||
public string WorkHoursTitleKey { get; set; }
|
|
||||||
public string WorkHoursTitleValue { get; set; }
|
|
||||||
public string WorkWeekdayKey { get; set; }
|
|
||||||
public string WorkWeekdayValue { get; set; }
|
|
||||||
public string WorkWeekendKey { get; set; }
|
|
||||||
public string WorkWeekendValue { get; set; }
|
|
||||||
public string WorkWhatsappKey { get; set; }
|
|
||||||
public string WorkWhatsappValue { get; set; }
|
|
||||||
public string WorkHoursStyleClass { get; set; }
|
|
||||||
|
|
||||||
public string MapTitleKey { get; set; }
|
|
||||||
public string MapTitleValue { get; set; }
|
|
||||||
public string MapSrc { get; set; }
|
|
||||||
public string MapWidth { get; set; }
|
|
||||||
public string MapHeight { get; set; }
|
|
||||||
public bool MapAllowFullScreen { get; set; }
|
|
||||||
public string MapLoading { get; set; }
|
|
||||||
public string MapReferrerPolicy { get; set; }
|
|
||||||
public string MapContainerStyleClass { get; set; }
|
|
||||||
public string MapFrameStyleClass { get; set; }
|
|
||||||
public List<SaveLocalizedTextInput> StyleTexts { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
using System;
|
|
||||||
using Volo.Abp.Application.Dtos;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Public;
|
|
||||||
|
|
||||||
public class ServiceDto : FullAuditedEntityDto<Guid>
|
|
||||||
{
|
|
||||||
public string? Icon { get; set; }
|
|
||||||
public string Title { get; set; }
|
|
||||||
public string? Description { get; set; }
|
|
||||||
public string Type { get; set; }
|
|
||||||
|
|
||||||
public string[] Features { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -66,6 +66,8 @@ public class CustomComponentAppService : CrudAppService<
|
||||||
|
|
||||||
public override async Task<CustomComponentDto> CreateAsync(CreateUpdateCustomComponentDto input)
|
public override async Task<CustomComponentDto> CreateAsync(CreateUpdateCustomComponentDto input)
|
||||||
{
|
{
|
||||||
|
NormalizeRoutePath(input);
|
||||||
|
|
||||||
var result = await base.CreateAsync(input);
|
var result = await base.CreateAsync(input);
|
||||||
await SyncDataSourcesAsync(result.Id);
|
await SyncDataSourcesAsync(result.Id);
|
||||||
await _seedDataSynchronizer.SyncRowAsync(ListFormCode, result.Id, SeedSyncOperation.Insert);
|
await _seedDataSynchronizer.SyncRowAsync(ListFormCode, result.Id, SeedSyncOperation.Insert);
|
||||||
|
|
@ -76,6 +78,8 @@ public class CustomComponentAppService : CrudAppService<
|
||||||
Guid id,
|
Guid id,
|
||||||
CreateUpdateCustomComponentDto input)
|
CreateUpdateCustomComponentDto input)
|
||||||
{
|
{
|
||||||
|
NormalizeRoutePath(input);
|
||||||
|
|
||||||
var existingComponent = await Repository.GetAsync(id);
|
var existingComponent = await Repository.GetAsync(id);
|
||||||
|
|
||||||
if (input.Props is null)
|
if (input.Props is null)
|
||||||
|
|
@ -90,6 +94,16 @@ public class CustomComponentAppService : CrudAppService<
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rotasi olmayan bilesen (ust bilgi, alt bilgi) icin yol bostur. Bos dize saklanirsa
|
||||||
|
/// <c>RoutePath</c> tekil indeksi ikinci rotasiz bilesende carpisir; deger NULL yazilir.
|
||||||
|
/// </summary>
|
||||||
|
private static void NormalizeRoutePath(CreateUpdateCustomComponentDto input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input.RoutePath))
|
||||||
|
input.RoutePath = null;
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task DeleteAsync(Guid id)
|
public override async Task DeleteAsync(Guid id)
|
||||||
{
|
{
|
||||||
await base.DeleteAsync(id);
|
await base.DeleteAsync(id);
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,10 @@ public class MenuAppService : CrudAppService<
|
||||||
await CheckGetPolicyAsync();
|
await CheckGetPolicyAsync();
|
||||||
|
|
||||||
var query = await _menuRepository.GetQueryableAsync();
|
var query = await _menuRepository.GetQueryableAsync();
|
||||||
query = query.Where(a => !a.IsDisabled && a.ModuleId != null);
|
// Public site menusu yonetim kenar cubugunda yer almaz; o agac ziyaretci
|
||||||
|
// duzenine aittir ve uygulama konfigurasyonu uzerinden tasinir.
|
||||||
|
query = query.Where(a => !a.IsDisabled && a.ModuleId != null &&
|
||||||
|
a.ModuleId != PlatformConsts.PublicSite.ModuleId);
|
||||||
|
|
||||||
var entities = await AsyncExecuter.ToListAsync(query);
|
var entities = await AsyncExecuter.ToListAsync(query);
|
||||||
var entityDtos = await MapToGetListOutputDtosAsync(entities);
|
var entityDtos = await MapToGetListOutputDtosAsync(entities);
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,11 @@ using static Sozsoft.Platform.Data.Seeds.SeedConsts;
|
||||||
namespace Sozsoft.Platform.Public;
|
namespace Sozsoft.Platform.Public;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Public site uc noktalari. Okuma metotlari anonim erisime aciktir; sayfa tasarimini
|
/// Public site uc noktalari: demo talebi, urun/odeme listeleri, siparis olusturma ve
|
||||||
/// degistiren <c>Save*PageAsync</c> metotlari <see cref="AppCodes.WebSiteDesign"/>
|
/// ulke/il/ilce aramalari. Tamami anonim erisime aciktir.
|
||||||
/// yetkileriyle korunur (yetkiler PermissionsData.json icinde tanimli, seeder ile yonetilir).
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PublicAppService : PlatformAppService
|
public class PublicAppService : PlatformAppService
|
||||||
{
|
{
|
||||||
private readonly IRepository<Service, Guid> _serviceRepository;
|
|
||||||
private readonly ISettingProvider _settingProvider;
|
private readonly ISettingProvider _settingProvider;
|
||||||
private readonly ISozsoftEmailSender _emailSender;
|
private readonly ISozsoftEmailSender _emailSender;
|
||||||
private readonly IRepository<Demo, Guid> _demoRepository;
|
private readonly IRepository<Demo, Guid> _demoRepository;
|
||||||
|
|
@ -37,18 +35,11 @@ public class PublicAppService : PlatformAppService
|
||||||
private readonly IRepository<PaymentMethod, Guid> _paymentMethodRepository;
|
private readonly IRepository<PaymentMethod, Guid> _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<Home, Guid> _homeRepository;
|
|
||||||
private readonly IRepository<Contact, Guid> _contactRepository;
|
|
||||||
private readonly IRepository<Country, string> _countryRepository;
|
private readonly IRepository<Country, string> _countryRepository;
|
||||||
private readonly IRepository<City, Guid> _cityRepository;
|
private readonly IRepository<City, Guid> _cityRepository;
|
||||||
private readonly IRepository<District, Guid> _districtRepository;
|
private readonly IRepository<District, Guid> _districtRepository;
|
||||||
private readonly IRepository<LanguageKey, Guid> _languageKeyRepository;
|
|
||||||
private readonly IRepository<LanguageText, Guid> _languageTextRepository;
|
|
||||||
private readonly LanguageTextAppService _languageTextAppService;
|
|
||||||
|
|
||||||
public PublicAppService(
|
public PublicAppService(
|
||||||
IRepository<Service, Guid> serviceRepository,
|
|
||||||
ISettingProvider settingProvider,
|
ISettingProvider settingProvider,
|
||||||
ISozsoftEmailSender emailSender,
|
ISozsoftEmailSender emailSender,
|
||||||
IRepository<Demo, Guid> demoRepository,
|
IRepository<Demo, Guid> demoRepository,
|
||||||
|
|
@ -58,18 +49,11 @@ public class PublicAppService : PlatformAppService
|
||||||
IRepository<PaymentMethod, Guid> paymentMethodRepository,
|
IRepository<PaymentMethod, Guid> paymentMethodRepository,
|
||||||
IRepository<InstallmentOption> installmentOptionRepository,
|
IRepository<InstallmentOption> installmentOptionRepository,
|
||||||
IRepository<Order, Guid> orderRepository,
|
IRepository<Order, Guid> orderRepository,
|
||||||
IRepository<About, Guid> aboutRepository,
|
|
||||||
IRepository<Home, Guid> homeRepository,
|
|
||||||
IRepository<Contact, Guid> contactRepository,
|
|
||||||
IRepository<Country, string> countryRepository,
|
IRepository<Country, string> countryRepository,
|
||||||
IRepository<City, Guid> cityRepository,
|
IRepository<City, Guid> cityRepository,
|
||||||
IRepository<District, Guid> districtRepository,
|
IRepository<District, Guid> districtRepository
|
||||||
IRepository<LanguageKey, Guid> languageKeyRepository,
|
|
||||||
IRepository<LanguageText, Guid> languageTextRepository,
|
|
||||||
LanguageTextAppService languageTextAppService
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_serviceRepository = serviceRepository;
|
|
||||||
_settingProvider = settingProvider;
|
_settingProvider = settingProvider;
|
||||||
_emailSender = emailSender;
|
_emailSender = emailSender;
|
||||||
_demoRepository = demoRepository;
|
_demoRepository = demoRepository;
|
||||||
|
|
@ -79,235 +63,11 @@ public class PublicAppService : PlatformAppService
|
||||||
_paymentMethodRepository = paymentMethodRepository;
|
_paymentMethodRepository = paymentMethodRepository;
|
||||||
_installmentOptionRepository = installmentOptionRepository;
|
_installmentOptionRepository = installmentOptionRepository;
|
||||||
_orderRepository = orderRepository;
|
_orderRepository = orderRepository;
|
||||||
_aboutRepository = aboutRepository;
|
|
||||||
_homeRepository = homeRepository;
|
|
||||||
_contactRepository = contactRepository;
|
|
||||||
_languageKeyRepository = languageKeyRepository;
|
|
||||||
_languageTextRepository = languageTextRepository;
|
|
||||||
_languageTextAppService = languageTextAppService;
|
|
||||||
_countryRepository = countryRepository;
|
_countryRepository = countryRepository;
|
||||||
_cityRepository = cityRepository;
|
_cityRepository = cityRepository;
|
||||||
_districtRepository = districtRepository;
|
_districtRepository = districtRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<ServiceDto>> GetServicesListAsync()
|
|
||||||
{
|
|
||||||
var queryable = await _serviceRepository.GetQueryableAsync();
|
|
||||||
var entity = await AsyncExecuter.ToListAsync(queryable.OrderBy(a => a.CreationTime));
|
|
||||||
|
|
||||||
return ObjectMapper.Map<List<Service>, List<ServiceDto>>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize(AppCodes.WebSiteDesign.About)]
|
|
||||||
public async Task SaveAboutPageAsync(SaveAboutPageInput input)
|
|
||||||
{
|
|
||||||
var entity = await _aboutRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(About));
|
|
||||||
|
|
||||||
entity.StatsJson = JsonSerializer.Serialize(input.Stats.Select(stat => new StatDto
|
|
||||||
{
|
|
||||||
Icon = stat.Icon,
|
|
||||||
Value = stat.Value,
|
|
||||||
LabelKey = stat.LabelKey,
|
|
||||||
UseCounter = stat.UseCounter,
|
|
||||||
CounterEnd = stat.CounterEnd,
|
|
||||||
CounterSuffix = stat.CounterSuffix,
|
|
||||||
CounterDuration = stat.CounterDuration,
|
|
||||||
}).ToList());
|
|
||||||
|
|
||||||
entity.DescriptionsJson = JsonSerializer.Serialize(input.Descriptions.Select(item => item.Key).ToList());
|
|
||||||
|
|
||||||
entity.SectionsJson = JsonSerializer.Serialize(input.Sections.Select(section => new SectionDto
|
|
||||||
{
|
|
||||||
Key = section.TitleKey,
|
|
||||||
DescKey = section.DescriptionKey,
|
|
||||||
}).ToList());
|
|
||||||
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroTitleKey, input.HeroTitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroSubtitleKey, input.HeroSubtitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroImageKey, input.HeroImageValue);
|
|
||||||
|
|
||||||
foreach (var stat in input.Stats)
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, stat.LabelKey, stat.LabelValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var description in input.Descriptions)
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, description.Key, description.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var section in input.Sections)
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, section.TitleKey, section.TitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, section.DescriptionKey, section.DescriptionValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var styleText in input.StyleTexts)
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, styleText.Key, styleText.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
await _aboutRepository.UpdateAsync(entity, autoSave: true);
|
|
||||||
await _languageTextAppService.ClearRedisCacheAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize(AppCodes.WebSiteDesign.Services)]
|
|
||||||
public async Task SaveServicesPageAsync(SaveServicesPageInput input)
|
|
||||||
{
|
|
||||||
var existingEntities = await _serviceRepository.GetListAsync();
|
|
||||||
|
|
||||||
foreach (var entity in existingEntities)
|
|
||||||
{
|
|
||||||
await _serviceRepository.DeleteAsync(entity, autoSave: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in input.ServiceItems.Concat(input.SupportItems))
|
|
||||||
{
|
|
||||||
var entity = new Service
|
|
||||||
{
|
|
||||||
Icon = item.Icon,
|
|
||||||
Title = item.TitleKey,
|
|
||||||
Description = item.DescriptionKey,
|
|
||||||
Type = item.Type,
|
|
||||||
Features = item.Features.Select(feature => feature.Key).ToArray(),
|
|
||||||
};
|
|
||||||
|
|
||||||
await _serviceRepository.InsertAsync(entity, autoSave: false);
|
|
||||||
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, item.TitleKey, item.TitleValue);
|
|
||||||
|
|
||||||
if (!item.DescriptionKey.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, item.DescriptionKey!, item.DescriptionValue ?? string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var feature in item.Features)
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, feature.Key, feature.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroTitleKey, input.HeroTitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroSubtitleKey, input.HeroSubtitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroImageKey, input.HeroImageValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.SupportTitleKey, input.SupportTitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.SupportButtonLabelKey, input.SupportButtonLabelValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleKey, input.CtaTitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.CtaDescriptionKey, input.CtaDescriptionValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue);
|
|
||||||
|
|
||||||
foreach (var styleText in input.StyleTexts)
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, styleText.Key, styleText.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
await CurrentUnitOfWork!.SaveChangesAsync();
|
|
||||||
await _languageTextAppService.ClearRedisCacheAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<HomeDto> GetHomeAsync()
|
|
||||||
{
|
|
||||||
var entity = await _homeRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Home));
|
|
||||||
return ObjectMapper.Map<Home, HomeDto>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize(AppCodes.WebSiteDesign.Home)]
|
|
||||||
public async Task SaveHomePageAsync(SaveHomePageInput input)
|
|
||||||
{
|
|
||||||
var entity = await _homeRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Home));
|
|
||||||
|
|
||||||
entity.HeroBackgroundImageKey = input.HeroBackgroundImageKey;
|
|
||||||
entity.HeroPrimaryCtaKey = input.HeroPrimaryCtaKey;
|
|
||||||
entity.HeroSecondaryCtaKey = input.HeroSecondaryCtaKey;
|
|
||||||
entity.FeaturesTitleKey = input.FeaturesTitleKey;
|
|
||||||
entity.FeaturesSubtitleKey = input.FeaturesSubtitleKey;
|
|
||||||
entity.SolutionsTitleKey = input.SolutionsTitleKey;
|
|
||||||
entity.SolutionsSubtitleKey = input.SolutionsSubtitleKey;
|
|
||||||
entity.CtaTitleKey = input.CtaTitleKey;
|
|
||||||
entity.CtaSubtitleKey = input.CtaSubtitleKey;
|
|
||||||
entity.CtaButtonLabelKey = input.CtaButtonLabelKey;
|
|
||||||
|
|
||||||
entity.SlidesJson = JsonSerializer.Serialize(input.Slides.Select(slide => new HomeSlideDto
|
|
||||||
{
|
|
||||||
TitleKey = slide.TitleKey,
|
|
||||||
SubtitleKey = slide.SubtitleKey,
|
|
||||||
StyleClass = slide.StyleClass,
|
|
||||||
Services = slide.Services.Select(service => new HomeSlideServiceDto
|
|
||||||
{
|
|
||||||
Icon = service.Icon,
|
|
||||||
TitleKey = service.TitleKey,
|
|
||||||
DescriptionKey = service.DescriptionKey,
|
|
||||||
StyleClass = service.StyleClass,
|
|
||||||
}).ToList(),
|
|
||||||
}).ToList());
|
|
||||||
|
|
||||||
entity.FeaturesJson = JsonSerializer.Serialize(input.Features.Select(feature => new HomeFeatureDto
|
|
||||||
{
|
|
||||||
Icon = feature.Icon,
|
|
||||||
TitleKey = feature.TitleKey,
|
|
||||||
DescriptionKey = feature.DescriptionKey,
|
|
||||||
StyleClass = feature.StyleClass,
|
|
||||||
}).ToList());
|
|
||||||
|
|
||||||
entity.SolutionsJson = JsonSerializer.Serialize(input.Solutions.Select(solution => new HomeSolutionDto
|
|
||||||
{
|
|
||||||
Icon = solution.Icon,
|
|
||||||
ColorClass = solution.ColorClass,
|
|
||||||
TitleKey = solution.TitleKey,
|
|
||||||
DescriptionKey = solution.DescriptionKey,
|
|
||||||
StyleClass = solution.StyleClass,
|
|
||||||
}).ToList());
|
|
||||||
|
|
||||||
await _homeRepository.UpdateAsync(entity, autoSave: false);
|
|
||||||
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroBackgroundImageKey, input.HeroBackgroundImageValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroPrimaryCtaKey, input.HeroPrimaryCtaValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroPrimaryCtaStyleKey, input.HeroPrimaryCtaStyleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaKey, input.HeroSecondaryCtaValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroSecondaryCtaStyleKey, input.HeroSecondaryCtaStyleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleKey, input.FeaturesTitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesTitleStyleKey, input.FeaturesTitleStyleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleKey, input.FeaturesSubtitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.FeaturesSubtitleStyleKey, input.FeaturesSubtitleStyleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleKey, input.SolutionsTitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsTitleStyleKey, input.SolutionsTitleStyleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleKey, input.SolutionsSubtitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.SolutionsSubtitleStyleKey, input.SolutionsSubtitleStyleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleKey, input.CtaTitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.CtaTitleStyleKey, input.CtaTitleStyleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleKey, input.CtaSubtitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.CtaSubtitleStyleKey, input.CtaSubtitleStyleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonLabelKey, input.CtaButtonLabelValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.CtaButtonStyleKey, input.CtaButtonStyleValue);
|
|
||||||
|
|
||||||
foreach (var slide in input.Slides)
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, slide.TitleKey, slide.TitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, slide.SubtitleKey, slide.SubtitleValue);
|
|
||||||
|
|
||||||
foreach (var service in slide.Services)
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, service.TitleKey, service.TitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, service.DescriptionKey, service.DescriptionValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var feature in input.Features)
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, feature.TitleKey, feature.TitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, feature.DescriptionKey, feature.DescriptionValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var solution in input.Solutions)
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, solution.TitleKey, solution.TitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, solution.DescriptionKey, solution.DescriptionValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
await CurrentUnitOfWork!.SaveChangesAsync();
|
|
||||||
await _languageTextAppService.ClearRedisCacheAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CreateDemoAsync(DemoDto input)
|
public async Task CreateDemoAsync(DemoDto input)
|
||||||
{
|
{
|
||||||
var demo = ObjectMapper.Map<DemoDto, Demo>(input);
|
var demo = ObjectMapper.Map<DemoDto, Demo>(input);
|
||||||
|
|
@ -565,7 +325,6 @@ public class PublicAppService : PlatformAppService
|
||||||
Categories = categoryDtos
|
Categories = categoryDtos
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<BlogPostDto> GetPostAsync(Guid id)
|
private async Task<BlogPostDto> GetPostAsync(Guid id)
|
||||||
{
|
{
|
||||||
// Tek sorguda post ve category'yi çek (N+1 önleme)
|
// Tek sorguda post ve category'yi çek (N+1 önleme)
|
||||||
|
|
@ -586,7 +345,6 @@ public class PublicAppService : PlatformAppService
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BlogPostDto> GetPostBySlugAsync(string slug)
|
public async Task<BlogPostDto> GetPostBySlugAsync(string slug)
|
||||||
{
|
{
|
||||||
var post = await _postRepository.FirstOrDefaultAsync(x => x.Slug == slug);
|
var post = await _postRepository.FirstOrDefaultAsync(x => x.Slug == slug);
|
||||||
|
|
@ -1043,19 +801,6 @@ public class PublicAppService : PlatformAppService
|
||||||
return WebUtility.HtmlEncode(value ?? string.Empty);
|
return WebUtility.HtmlEncode(value ?? string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<CountryDto>> GetCountryAsync()
|
public async Task<List<CountryDto>> GetCountryAsync()
|
||||||
{
|
{
|
||||||
var queryable = await _countryRepository.GetQueryableAsync();
|
var queryable = await _countryRepository.GetQueryableAsync();
|
||||||
|
|
@ -1128,121 +873,6 @@ public class PublicAppService : PlatformAppService
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(AppCodes.WebSiteDesign.Contact)]
|
|
||||||
public async Task SaveContactPageAsync(SaveContactPageInput input)
|
|
||||||
{
|
|
||||||
var entity = await _contactRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Contact));
|
|
||||||
|
|
||||||
entity.Address = input.AddressKey;
|
|
||||||
entity.PhoneNumber = input.PhoneNumber;
|
|
||||||
entity.Email = input.Email;
|
|
||||||
entity.Location = input.Location;
|
|
||||||
entity.TaxNumber = long.TryParse(input.TaxNumber, out var taxNumber) ? taxNumber : null;
|
|
||||||
|
|
||||||
entity.BankJson = JsonSerializer.Serialize(new BankDto
|
|
||||||
{
|
|
||||||
AccountHolder = input.BankAccountHolder,
|
|
||||||
Branch = input.BankBranch,
|
|
||||||
AccountNumber = input.BankAccountNumber,
|
|
||||||
Iban = input.BankIban,
|
|
||||||
StyleClass = input.BankStyleClass,
|
|
||||||
});
|
|
||||||
|
|
||||||
entity.WorkHoursJson = JsonSerializer.Serialize(new WorkHoursDto
|
|
||||||
{
|
|
||||||
Weekday = input.WorkWeekdayKey,
|
|
||||||
Weekend = input.WorkWeekendKey,
|
|
||||||
Whatsapp = input.WorkWhatsappKey,
|
|
||||||
StyleClass = input.WorkHoursStyleClass,
|
|
||||||
});
|
|
||||||
|
|
||||||
entity.MapJson = JsonSerializer.Serialize(new MapDto
|
|
||||||
{
|
|
||||||
Title = input.MapTitleKey,
|
|
||||||
Src = input.MapSrc,
|
|
||||||
Width = input.MapWidth,
|
|
||||||
Height = input.MapHeight,
|
|
||||||
AllowFullScreen = input.MapAllowFullScreen,
|
|
||||||
Loading = input.MapLoading,
|
|
||||||
ReferrerPolicy = input.MapReferrerPolicy,
|
|
||||||
ContainerStyleClass = input.MapContainerStyleClass,
|
|
||||||
FrameStyleClass = input.MapFrameStyleClass,
|
|
||||||
});
|
|
||||||
|
|
||||||
await _contactRepository.UpdateAsync(entity, autoSave: false);
|
|
||||||
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroTitleKey, input.HeroTitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroSubtitleKey, input.HeroSubtitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.HeroImageKey, input.HeroImageValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.ContactInfoTitleKey, input.ContactInfoTitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.AddressKey, input.AddressValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.BankTitleKey, input.BankTitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.WorkHoursTitleKey, input.WorkHoursTitleValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.WorkWeekdayKey, input.WorkWeekdayValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.WorkWeekendKey, input.WorkWeekendValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.WorkWhatsappKey, input.WorkWhatsappValue);
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, input.MapTitleKey, input.MapTitleValue);
|
|
||||||
|
|
||||||
foreach (var styleText in input.StyleTexts)
|
|
||||||
{
|
|
||||||
await UpsertLanguageTextAsync(input.CultureName, styleText.Key, styleText.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
await CurrentUnitOfWork!.SaveChangesAsync();
|
|
||||||
await _languageTextAppService.ClearRedisCacheAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpsertLanguageTextAsync(string cultureName, string key, string value)
|
|
||||||
{
|
|
||||||
if (key.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var normalizedCultureName = NormalizeCultureName(cultureName);
|
|
||||||
var resourceName = PlatformConsts.AppName;
|
|
||||||
|
|
||||||
var languageKey = await _languageKeyRepository.FirstOrDefaultAsync(a => a.ResourceName == resourceName && a.Key == key);
|
|
||||||
if (languageKey == null)
|
|
||||||
{
|
|
||||||
languageKey = await _languageKeyRepository.InsertAsync(new LanguageKey
|
|
||||||
{
|
|
||||||
ResourceName = resourceName,
|
|
||||||
Key = key,
|
|
||||||
}, autoSave: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var languageText = await _languageTextRepository.FirstOrDefaultAsync(a =>
|
|
||||||
a.ResourceName == resourceName &&
|
|
||||||
a.Key == languageKey.Key &&
|
|
||||||
a.CultureName == normalizedCultureName);
|
|
||||||
|
|
||||||
if (languageText == null)
|
|
||||||
{
|
|
||||||
await _languageTextRepository.InsertAsync(new LanguageText
|
|
||||||
{
|
|
||||||
ResourceName = resourceName,
|
|
||||||
Key = languageKey.Key,
|
|
||||||
CultureName = normalizedCultureName,
|
|
||||||
Value = value ?? string.Empty,
|
|
||||||
}, autoSave: false);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
languageText.Value = value ?? string.Empty;
|
|
||||||
await _languageTextRepository.UpdateAsync(languageText, autoSave: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeCultureName(string cultureName)
|
|
||||||
{
|
|
||||||
if (cultureName.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return PlatformConsts.DefaultLanguage;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cultureName.Split('-')[0].ToLowerInvariant();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,22 +33,6 @@ public partial class DemoToDemoDtoMapper : TwoWayMapperBase<Demo, DemoDto>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
|
||||||
public partial class ServiceToServiceDtoMapper : MapperBase<Service, ServiceDto>
|
|
||||||
{
|
|
||||||
public override partial ServiceDto Map(Service source);
|
|
||||||
|
|
||||||
public override partial void Map(Service source, ServiceDto destination);
|
|
||||||
|
|
||||||
public override void BeforeMap(Service source)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void AfterMap(Service source, ServiceDto destination)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||||
public partial class BlogCategoryToBlogCategoryDtoMapper : MapperBase<BlogCategory, BlogCategoryDto>
|
public partial class BlogCategoryToBlogCategoryDtoMapper : MapperBase<BlogCategory, BlogCategoryDto>
|
||||||
{
|
{
|
||||||
|
|
@ -173,54 +157,6 @@ public partial class OrderToOrderDtoMapper : MapperBase<Order, OrderDto>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
|
||||||
public partial class AboutToAboutDtoMapper : MapperBase<About, AboutDto>
|
|
||||||
{
|
|
||||||
public override partial AboutDto Map(About source);
|
|
||||||
|
|
||||||
public override partial void Map(About source, AboutDto destination);
|
|
||||||
|
|
||||||
public override void BeforeMap(About source)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void AfterMap(About source, AboutDto destination)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
|
||||||
public partial class HomeToHomeDtoMapper : MapperBase<Home, HomeDto>
|
|
||||||
{
|
|
||||||
public override partial HomeDto Map(Home source);
|
|
||||||
|
|
||||||
public override partial void Map(Home source, HomeDto destination);
|
|
||||||
|
|
||||||
public override void BeforeMap(Home source)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void AfterMap(Home source, HomeDto destination)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
|
||||||
public partial class ContactToContactDtoMapper : MapperBase<Contact, ContactDto>
|
|
||||||
{
|
|
||||||
public override partial ContactDto Map(Contact source);
|
|
||||||
|
|
||||||
public override partial void Map(Contact source, ContactDto destination);
|
|
||||||
|
|
||||||
public override void BeforeMap(Contact source)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void AfterMap(Contact source, ContactDto destination)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||||
public partial class CountryToCountryDtoMapper : MapperBase<Country, CountryDto>
|
public partial class CountryToCountryDtoMapper : MapperBase<Country, CountryDto>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1194,6 +1194,12 @@
|
||||||
"en": "Web Site",
|
"en": "Web Site",
|
||||||
"tr": "Web Site"
|
"tr": "Web Site"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Public.Header",
|
||||||
|
"en": "Site Menu",
|
||||||
|
"tr": "Site Menüsü"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.IdentityManagement.ClaimType",
|
"key": "App.IdentityManagement.ClaimType",
|
||||||
|
|
@ -7266,6 +7272,12 @@
|
||||||
"tr": "Vergi Dairesi",
|
"tr": "Vergi Dairesi",
|
||||||
"en": "Tax Office"
|
"en": "Tax Office"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.PublicContact.WorkHours",
|
||||||
|
"en": "Working Hours",
|
||||||
|
"tr": "Çalışma Saatleri"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.PublicContact.TransferForm",
|
"key": "App.PublicContact.TransferForm",
|
||||||
|
|
@ -8934,90 +8946,6 @@
|
||||||
"tr": "Müşteri Yorumları",
|
"tr": "Müşteri Yorumları",
|
||||||
"en": "Testimonials"
|
"en": "Testimonials"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.DesignerTitle",
|
|
||||||
"tr": "Tasarımcı",
|
|
||||||
"en": "Designer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.IkonAnahtari",
|
|
||||||
"tr": "İkon Anahtarı",
|
|
||||||
"en": "Icon Key"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.ShowPanel",
|
|
||||||
"tr": "Paneli Göster",
|
|
||||||
"en": "Show Panel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.NoSelection",
|
|
||||||
"tr": "Bir alan seçin",
|
|
||||||
"en": "Select a field"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.DesignerDesc1",
|
|
||||||
"tr": "İkon, Başlık, Açıklama ve Özellik listesini düzenleyin.",
|
|
||||||
"en": "Edit the icon, title, description, and feature list."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.DesignerDesc2",
|
|
||||||
"tr": "Alt CTA metinlerini ve buton etiketini düzenleyin.",
|
|
||||||
"en": "Edit the sub-CTA texts and button label."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.DesignerDesc3",
|
|
||||||
"tr": "Destek alanı başlığını ve buton metnini düzenleyin.",
|
|
||||||
"en": "Edit the support area title and button text."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.DesignerDesc4",
|
|
||||||
"tr": "Başlık, alt başlık ve arka plan görselini düzenleyin.",
|
|
||||||
"en": "Edit the title, subtitle, and background image."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.DesignerDesc5",
|
|
||||||
"tr": "İletişim bilgilerini düzenleyin.",
|
|
||||||
"en": "Edit the contact information."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.DesignerDesc6",
|
|
||||||
"tr": "Banka bilgilerini düzenleyin.",
|
|
||||||
"en": "Edit the bank information."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.DesignerDesc7",
|
|
||||||
"tr": "Çalışma saatleri metinlerini düzenleyin.",
|
|
||||||
"en": "Edit the work hours information."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.DesignerDesc8",
|
|
||||||
"tr": "Harita başlığı ve iframe ayarlarını düzenleyin.",
|
|
||||||
"en": "Edit the map title and iframe settings."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.SelectField",
|
|
||||||
"tr": "Sayfa üzerinden bir blok seçerek özelliklerini buradan düzenleyin.",
|
|
||||||
"en": "Select a block on the page to edit its properties here."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.NoSelectionDetails",
|
|
||||||
"tr": "Seçili bölümün düzenlenebilir alanları burada gösterilir.",
|
|
||||||
"en": "Select a block on the page to edit its properties here."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.Listform.ListformField.DataSources",
|
"key": "App.Listform.ListformField.DataSources",
|
||||||
|
|
@ -24078,12 +24006,6 @@
|
||||||
"en": "Blog post not found.",
|
"en": "Blog post not found.",
|
||||||
"tr": "Blog yazısı bulunamadı."
|
"tr": "Blog yazısı bulunamadı."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.PublicDesigner.IconPlaceholder",
|
|
||||||
"en": "Example: {icon}",
|
|
||||||
"tr": "Örnek: {icon}"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.PublicPayment.CreateOrderError",
|
"key": "App.PublicPayment.CreateOrderError",
|
||||||
|
|
@ -24240,6 +24162,60 @@
|
||||||
"en": "Date picker component",
|
"en": "Date picker component",
|
||||||
"tr": "Tarih seçici bileşeni"
|
"tr": "Tarih seçici bileşeni"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.CatalogPublicHeaderBar.PublicHeaderBarDescription",
|
||||||
|
"en": "Public site header bar — swaps its classes when the page is scrolled",
|
||||||
|
"tr": "Public site üst bilgi çubuğu — sayfa kaydırıldığında sınıflarını değiştirir"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.CatalogPublicNav.PublicNavDescription",
|
||||||
|
"en": "Public site menu links (Menu Manager, Pub module)",
|
||||||
|
"tr": "Public site menü bağlantıları (Menü Yöneticisi, Pub modülü)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.CatalogPublicMobileMenu.PublicMobileMenuDescription",
|
||||||
|
"en": "Mobile menu button and the panel it opens; the panel content is designed here",
|
||||||
|
"tr": "Mobil menü düğmesi ve açtığı panel; panelin içeriği burada tasarlanır"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.CatalogPublicLogo.PublicLogoDescription",
|
||||||
|
"en": "Logo that follows the active theme",
|
||||||
|
"tr": "Aktif temayı izleyen logo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.CatalogPublicThemeToggle.PublicThemeToggleDescription",
|
||||||
|
"en": "Light/dark theme switch",
|
||||||
|
"tr": "Aydınlık/karanlık tema anahtarı"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.CatalogPublicLanguageSelector.PublicLanguageSelectorDescription",
|
||||||
|
"en": "Language selector",
|
||||||
|
"tr": "Dil seçici"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.CatalogPublicDemoButton.PublicDemoButtonDescription",
|
||||||
|
"en": "Button that opens the demo window",
|
||||||
|
"tr": "Demo penceresini açan düğme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.CatalogPlatformIcon.PlatformIconDescription",
|
||||||
|
"en": "Icon drawn by name (FaUser, FcHome …)",
|
||||||
|
"tr": "Ada göre çizilen ikon (FaUser, FcHome …)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.ComponentLink.LinkDescription",
|
||||||
|
"en": "Link (a) — navigates to the given address",
|
||||||
|
"tr": "Bağlantı (a) — verilen adrese gider"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.ComponentDiv.DivDescription",
|
"key": "App.ComponentDiv.DivDescription",
|
||||||
|
|
|
||||||
|
|
@ -7726,10 +7726,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
|
||||||
AllowSearch = true,
|
AllowSearch = true,
|
||||||
// Rota "/" ile baslamalidir; sunucu tarafinda da ayni kural dogrulanir.
|
// Rota bos birakilabilir (rotasi olmayan bilesen); dolduruldugunda "/" ile
|
||||||
|
// baslamalidir. Ayni kural sunucu tarafinda da dogrulanir.
|
||||||
ValidationRuleJson = JsonSerializer.Serialize(new ValidationRuleDto[] {
|
ValidationRuleJson = JsonSerializer.Serialize(new ValidationRuleDto[] {
|
||||||
new() { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) },
|
new() { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.pattern), Pattern = "^(/.*)?$", Message = "Route path must start with /" },
|
||||||
new() { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.pattern), Pattern = "^/.*", Message = "Route path must start with /" },
|
|
||||||
}),
|
}),
|
||||||
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
|
@ -7781,13 +7781,16 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Dependencies",
|
FieldName = "Dependencies",
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
|
||||||
CaptionName = "App.Platform.Dependencies",
|
CaptionName = "App.Platform.Dependencies",
|
||||||
|
EditGroupOrderNo = 1,
|
||||||
|
EditOrderNo = 5,
|
||||||
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
|
ColSpan = 1,
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 6,
|
ListOrderNo = 6,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
|
||||||
AllowSearch = true,
|
AllowSearch = true,
|
||||||
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ public class MenuSeedDto
|
||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
public string Icon { get; set; }
|
public string Icon { get; set; }
|
||||||
|
public string CssClass { get; set; }
|
||||||
public string RequiredPermissionName { get; set; }
|
public string RequiredPermissionName { get; set; }
|
||||||
public bool IsDisabled { get; set; }
|
public bool IsDisabled { get; set; }
|
||||||
public string ModuleId { get; set; }
|
public string ModuleId { get; set; }
|
||||||
|
|
@ -120,6 +121,7 @@ public class MenuDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
Order = item.Order,
|
Order = item.Order,
|
||||||
Url = item.Url,
|
Url = item.Url,
|
||||||
Icon = item.Icon,
|
Icon = item.Icon,
|
||||||
|
CssClass = item.CssClass,
|
||||||
RequiredPermissionName = item.RequiredPermissionName,
|
RequiredPermissionName = item.RequiredPermissionName,
|
||||||
IsDisabled = item.IsDisabled,
|
IsDisabled = item.IsDisabled,
|
||||||
ModuleId = item.ModuleId
|
ModuleId = item.ModuleId
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,5 @@
|
||||||
{
|
{
|
||||||
"Routes": [
|
"Routes": [
|
||||||
{
|
|
||||||
"key": "home",
|
|
||||||
"path": "/home",
|
|
||||||
"componentType": "normal",
|
|
||||||
"componentPath": "@/views/public/Home",
|
|
||||||
"routeType": "public",
|
|
||||||
"authority": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "about",
|
|
||||||
"path": "/about",
|
|
||||||
"componentType": "normal",
|
|
||||||
"componentPath": "@/views/public/About",
|
|
||||||
"routeType": "public",
|
|
||||||
"authority": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "products",
|
"key": "products",
|
||||||
"path": "/products",
|
"path": "/products",
|
||||||
|
|
@ -48,14 +32,6 @@
|
||||||
"routeType": "public",
|
"routeType": "public",
|
||||||
"authority": []
|
"authority": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "services",
|
|
||||||
"path": "/services",
|
|
||||||
"componentType": "normal",
|
|
||||||
"componentPath": "@/views/public/Services",
|
|
||||||
"routeType": "public",
|
|
||||||
"authority": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "blog",
|
"key": "blog",
|
||||||
"path": "/blog",
|
"path": "/blog",
|
||||||
|
|
@ -80,14 +56,6 @@
|
||||||
"routeType": "public",
|
"routeType": "public",
|
||||||
"authority": []
|
"authority": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "contact",
|
|
||||||
"path": "/contact",
|
|
||||||
"componentType": "normal",
|
|
||||||
"componentPath": "@/views/public/Contact",
|
|
||||||
"routeType": "public",
|
|
||||||
"authority": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "access-denied",
|
"key": "access-denied",
|
||||||
"path": "/access-denied",
|
"path": "/access-denied",
|
||||||
|
|
@ -370,38 +338,6 @@
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": []
|
"authority": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "homeDesigner",
|
|
||||||
"path": "/admin/public/home/designer",
|
|
||||||
"componentType": "normal",
|
|
||||||
"componentPath": "@/views/public/Home",
|
|
||||||
"routeType": "protected",
|
|
||||||
"authority": ["App.Home"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "aboutDesigner",
|
|
||||||
"path": "/admin/public/about/designer",
|
|
||||||
"componentType": "normal",
|
|
||||||
"componentPath": "@/views/public/About",
|
|
||||||
"routeType": "protected",
|
|
||||||
"authority": ["App.About"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "servicesDesigner",
|
|
||||||
"path": "/admin/public/services/designer",
|
|
||||||
"componentType": "normal",
|
|
||||||
"componentPath": "@/views/public/Services",
|
|
||||||
"routeType": "protected",
|
|
||||||
"authority": ["App.Services"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "contactDesigner",
|
|
||||||
"path": "/admin/public/contact/designer",
|
|
||||||
"componentType": "normal",
|
|
||||||
"componentPath": "@/views/public/Contact",
|
|
||||||
"routeType": "protected",
|
|
||||||
"authority": ["App.Contact"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "admin.videoroom",
|
"key": "admin.videoroom",
|
||||||
"path": "/admin/videoroom/dashboard",
|
"path": "/admin/videoroom/dashboard",
|
||||||
|
|
@ -433,6 +369,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Name": "Adm"
|
"Name": "Adm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Pub"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Menus": [
|
"Menus": [
|
||||||
|
|
@ -590,39 +529,6 @@
|
||||||
"IsDisabled": false,
|
"IsDisabled": false,
|
||||||
"ModuleId": "Sas"
|
"ModuleId": "Sas"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ParentCode": "App.Public",
|
|
||||||
"Code": "App.Home",
|
|
||||||
"DisplayName": "App.Home",
|
|
||||||
"Order": 0,
|
|
||||||
"Url": "/admin/public/home/designer",
|
|
||||||
"Icon": "FcHome",
|
|
||||||
"RequiredPermissionName": "App.Home",
|
|
||||||
"IsDisabled": false,
|
|
||||||
"ModuleId": "Sas"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ParentCode": "App.Public",
|
|
||||||
"Code": "App.About",
|
|
||||||
"DisplayName": "App.About",
|
|
||||||
"Order": 1,
|
|
||||||
"Url": "/admin/public/about/designer",
|
|
||||||
"Icon": "FcAbout",
|
|
||||||
"RequiredPermissionName": "App.About",
|
|
||||||
"IsDisabled": false,
|
|
||||||
"ModuleId": "Sas"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ParentCode": "App.Public",
|
|
||||||
"Code": "App.Services",
|
|
||||||
"DisplayName": "App.Services",
|
|
||||||
"Order": 2,
|
|
||||||
"Url": "/admin/public/services/designer",
|
|
||||||
"Icon": "FcServices",
|
|
||||||
"RequiredPermissionName": "App.Services",
|
|
||||||
"IsDisabled": false,
|
|
||||||
"ModuleId": "Sas"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Public",
|
"ParentCode": "App.Public",
|
||||||
"Code": "App.Orders.Products",
|
"Code": "App.Orders.Products",
|
||||||
|
|
@ -700,17 +606,6 @@
|
||||||
"IsDisabled": false,
|
"IsDisabled": false,
|
||||||
"ModuleId": "Sas"
|
"ModuleId": "Sas"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ParentCode": "App.Public",
|
|
||||||
"Code": "App.Contact",
|
|
||||||
"DisplayName": "App.Contact",
|
|
||||||
"Order": 10,
|
|
||||||
"Url": "/admin/public/contact/designer",
|
|
||||||
"Icon": "FcContacts",
|
|
||||||
"RequiredPermissionName": "App.Contact",
|
|
||||||
"IsDisabled": false,
|
|
||||||
"ModuleId": "Sas"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Saas",
|
"ParentCode": "App.Saas",
|
||||||
"Code": "App.DevRequests",
|
"Code": "App.DevRequests",
|
||||||
|
|
@ -1470,6 +1365,85 @@
|
||||||
"RequiredPermissionName": "App.ForumManagement.Publish",
|
"RequiredPermissionName": "App.ForumManagement.Publish",
|
||||||
"IsDisabled": false,
|
"IsDisabled": false,
|
||||||
"ModuleId": "Adm"
|
"ModuleId": "Adm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": null,
|
||||||
|
"Code": "App.Public.Header",
|
||||||
|
"DisplayName": "App.Public.Header",
|
||||||
|
"Order": 900,
|
||||||
|
"Url": null,
|
||||||
|
"Icon": "FcGlobe",
|
||||||
|
"RequiredPermissionName": null,
|
||||||
|
"IsDisabled": false,
|
||||||
|
"ModuleId": "Pub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.Public.Header",
|
||||||
|
"Code": "App.Public.Header.Home",
|
||||||
|
"DisplayName": "App.Home",
|
||||||
|
"Order": 10,
|
||||||
|
"Url": "/home",
|
||||||
|
"Icon": "FaHome",
|
||||||
|
"RequiredPermissionName": null,
|
||||||
|
"IsDisabled": false,
|
||||||
|
"ModuleId": "Pub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.Public.Header",
|
||||||
|
"Code": "App.Public.Header.About",
|
||||||
|
"DisplayName": "App.About",
|
||||||
|
"Order": 20,
|
||||||
|
"Url": "/about",
|
||||||
|
"Icon": "FaInfoCircle",
|
||||||
|
"RequiredPermissionName": null,
|
||||||
|
"IsDisabled": false,
|
||||||
|
"ModuleId": "Pub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.Public.Header",
|
||||||
|
"Code": "App.Public.Header.Products",
|
||||||
|
"DisplayName": "App.Platform.Orders",
|
||||||
|
"Order": 40,
|
||||||
|
"Url": "/products",
|
||||||
|
"Icon": "FaBoxOpen",
|
||||||
|
"RequiredPermissionName": null,
|
||||||
|
"IsDisabled": false,
|
||||||
|
"ModuleId": "Pub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.Public.Header",
|
||||||
|
"Code": "App.Public.Header.Blog",
|
||||||
|
"DisplayName": "App.Platform.BlogManagement",
|
||||||
|
"Order": 50,
|
||||||
|
"Url": "/blog",
|
||||||
|
"Icon": "FaBookOpen",
|
||||||
|
"RequiredPermissionName": null,
|
||||||
|
"IsDisabled": false,
|
||||||
|
"ModuleId": "Pub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.Public.Header",
|
||||||
|
"Code": "App.Public.Header.Contact",
|
||||||
|
"DisplayName": "App.Contact",
|
||||||
|
"Order": 60,
|
||||||
|
"Url": "/contact",
|
||||||
|
"Icon": "FaPhoneAlt",
|
||||||
|
"RequiredPermissionName": null,
|
||||||
|
"IsDisabled": false,
|
||||||
|
"ModuleId": "Pub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.Public.Header",
|
||||||
|
"Code": "App.Public.Header.Login",
|
||||||
|
"DisplayName": "App.PublicNav.Giris",
|
||||||
|
"Order": 900,
|
||||||
|
"Url": "/login",
|
||||||
|
"Icon": null,
|
||||||
|
"CssClass": "cta",
|
||||||
|
"RequiredPermissionName": null,
|
||||||
|
"IsDisabled": false,
|
||||||
|
"ModuleId": "Pub"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -811,60 +811,6 @@
|
||||||
"MultiTenancySide": 2,
|
"MultiTenancySide": 2,
|
||||||
"Modules": "Sas|Adm"
|
"Modules": "Sas|Adm"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.Home",
|
|
||||||
"ParentName": "App.Public",
|
|
||||||
"DisplayName": "App.Home",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"Modules": "Sas|Adm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.Home.Design",
|
|
||||||
"ParentName": "App.Home",
|
|
||||||
"DisplayName": "Design",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"Modules": "Sas|Adm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.About",
|
|
||||||
"ParentName": "App.Public",
|
|
||||||
"DisplayName": "App.About",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"Modules": "Sas|Adm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.About.Design",
|
|
||||||
"ParentName": "App.About",
|
|
||||||
"DisplayName": "Design",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"Modules": "Sas|Adm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.Services",
|
|
||||||
"ParentName": "App.Public",
|
|
||||||
"DisplayName": "App.Services",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"Modules": "Sas|Adm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.Services.Design",
|
|
||||||
"ParentName": "App.Services",
|
|
||||||
"DisplayName": "Design",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"Modules": "Sas|Adm"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"GroupName": "App.Saas",
|
"GroupName": "App.Saas",
|
||||||
"Name": "App.Orders.Products",
|
"Name": "App.Orders.Products",
|
||||||
|
|
@ -1315,24 +1261,6 @@
|
||||||
"MultiTenancySide": 2,
|
"MultiTenancySide": 2,
|
||||||
"Modules": "Sas|Adm"
|
"Modules": "Sas|Adm"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.Contact",
|
|
||||||
"ParentName": "App.Public",
|
|
||||||
"DisplayName": "App.Contact",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"Modules": "Sas|Adm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"GroupName": "App.Saas",
|
|
||||||
"Name": "App.Contact.Design",
|
|
||||||
"ParentName": "App.Contact",
|
|
||||||
"DisplayName": "Design",
|
|
||||||
"IsEnabled": true,
|
|
||||||
"MultiTenancySide": 2,
|
|
||||||
"Modules": "Sas|Adm"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"GroupName": "App.Saas",
|
"GroupName": "App.Saas",
|
||||||
"Name": "App.DevRequests",
|
"Name": "App.DevRequests",
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,6 @@ public enum TableNameEnum
|
||||||
UomCategory,
|
UomCategory,
|
||||||
Uom,
|
Uom,
|
||||||
WorkHour,
|
WorkHour,
|
||||||
About,
|
|
||||||
Home,
|
|
||||||
Service,
|
|
||||||
Product,
|
Product,
|
||||||
PaymentMethod,
|
PaymentMethod,
|
||||||
InstallmentOption,
|
InstallmentOption,
|
||||||
|
|
@ -67,7 +64,6 @@ public enum TableNameEnum
|
||||||
BlogCategory,
|
BlogCategory,
|
||||||
BlogPost,
|
BlogPost,
|
||||||
Demo,
|
Demo,
|
||||||
Contact,
|
|
||||||
Announcement,
|
Announcement,
|
||||||
AnnouncementComment,
|
AnnouncementComment,
|
||||||
AnnouncementLike,
|
AnnouncementLike,
|
||||||
|
|
|
||||||
|
|
@ -522,10 +522,6 @@ public static class PlatformConsts
|
||||||
public const string CrudEndpoints = Default + ".CrudEndpoints";
|
public const string CrudEndpoints = Default + ".CrudEndpoints";
|
||||||
}
|
}
|
||||||
|
|
||||||
//Web Site
|
|
||||||
public const string Home = Prefix.App + ".Home";
|
|
||||||
public const string About = Prefix.App + ".About";
|
|
||||||
public const string Services = Prefix.App + ".Services";
|
|
||||||
public static class Orders
|
public static class Orders
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".Orders";
|
public const string Default = Prefix.App + ".Orders";
|
||||||
|
|
@ -544,19 +540,6 @@ public static class PlatformConsts
|
||||||
public const string BlogCategory = Default + ".Category";
|
public const string BlogCategory = Default + ".Category";
|
||||||
}
|
}
|
||||||
public const string Demos = Prefix.App + ".Demos";
|
public const string Demos = Prefix.App + ".Demos";
|
||||||
public const string Contact = Prefix.App + ".Contact";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Public sitedeki sayfa tasarim modu (`?design=1`) yetkileri.
|
|
||||||
/// Yetkiler <c>Migrations/PermissionsData.json</c> icinde tanimlanir ve seeder ile yonetilir.
|
|
||||||
/// </summary>
|
|
||||||
public static class WebSiteDesign
|
|
||||||
{
|
|
||||||
public const string Home = AppCodes.Home + ".Design";
|
|
||||||
public const string About = AppCodes.About + ".Design";
|
|
||||||
public const string Services = AppCodes.Services + ".Design";
|
|
||||||
public const string Contact = AppCodes.Contact + ".Design";
|
|
||||||
}
|
|
||||||
|
|
||||||
//Administration
|
//Administration
|
||||||
public const string Administration = Prefix.App + ".Administration";
|
public const string Administration = Prefix.App + ".Administration";
|
||||||
|
|
@ -774,6 +757,23 @@ public static class PlatformConsts
|
||||||
public const string ColumnLookupTagBoxEditorApplyValueModeUseButtons = "useButtons";
|
public const string ColumnLookupTagBoxEditorApplyValueModeUseButtons = "useButtons";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Public site menusunun sozlesmesi. Menuler <see cref="ModuleId"/> modulu altinda
|
||||||
|
/// tutulur; <see cref="HeaderCode"/> altindaki kayitlar ust menuyu besler. Alt bilgi
|
||||||
|
/// menuden beslenmez, kendi Custom Component'inde tasarlanir.
|
||||||
|
/// </summary>
|
||||||
|
public static class PublicSite
|
||||||
|
{
|
||||||
|
public const string ModuleId = "Pub";
|
||||||
|
public const string HeaderCode = "App.Public.Header";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Menunun <c>CssClass</c> alaninda bu belirtec varsa baglanti duz yazi yerine
|
||||||
|
/// vurgulu buton olarak cizilir (orn. "Giris").
|
||||||
|
/// </summary>
|
||||||
|
public const string CallToActionClass = "cta";
|
||||||
|
}
|
||||||
|
|
||||||
public static class Urls
|
public static class Urls
|
||||||
{
|
{
|
||||||
public const string EmailConfirmation = "Account.EmailConfirmation";
|
public const string EmailConfirmation = "Account.EmailConfirmation";
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,6 @@ public static class TableNameResolver
|
||||||
{ nameof(TableNameEnum.ForumCategory), (TablePrefix.TenantByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.ForumCategory), (TablePrefix.TenantByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.ForumTopic), (TablePrefix.TenantByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.ForumTopic), (TablePrefix.TenantByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.ForumPost), (TablePrefix.TenantByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.ForumPost), (TablePrefix.TenantByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.Home), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
|
||||||
{ nameof(TableNameEnum.About), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
|
||||||
{ nameof(TableNameEnum.Service), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
|
||||||
{ nameof(TableNameEnum.Product), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.Product), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.PaymentMethod), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.PaymentMethod), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.InstallmentOption), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.InstallmentOption), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
|
|
@ -64,7 +61,6 @@ public static class TableNameResolver
|
||||||
{ nameof(TableNameEnum.BlogCategory), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.BlogCategory), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.BlogPost), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.BlogPost), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.Demo), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.Demo), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.Contact), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
|
||||||
{ nameof(TableNameEnum.Route), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.Route), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.Module), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.Module), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
{ nameof(TableNameEnum.Menu), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
{ nameof(TableNameEnum.Menu), (TablePrefix.PlatformByName, MenuPrefix.Sas) },
|
||||||
|
|
|
||||||
|
|
@ -422,10 +422,6 @@ public static class SeedConsts
|
||||||
public const string Licence = Prefix.App + ".Licence";
|
public const string Licence = Prefix.App + ".Licence";
|
||||||
public const string LicenceItem = Prefix.App + ".LicenceItem";
|
public const string LicenceItem = Prefix.App + ".LicenceItem";
|
||||||
|
|
||||||
//Web Site
|
|
||||||
public const string Home = Prefix.App + ".Home";
|
|
||||||
public const string About = Prefix.App + ".About";
|
|
||||||
public const string Services = Prefix.App + ".Services";
|
|
||||||
public static class Orders
|
public static class Orders
|
||||||
{
|
{
|
||||||
public const string Default = Prefix.App + ".Orders";
|
public const string Default = Prefix.App + ".Orders";
|
||||||
|
|
@ -444,19 +440,6 @@ public static class SeedConsts
|
||||||
public const string BlogCategory = Default + ".Category";
|
public const string BlogCategory = Default + ".Category";
|
||||||
}
|
}
|
||||||
public const string Demos = Prefix.App + ".Demos";
|
public const string Demos = Prefix.App + ".Demos";
|
||||||
public const string Contact = Prefix.App + ".Contact";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Public sitedeki sayfa tasarim modu (`?design=1`) yetkileri.
|
|
||||||
/// Yetkiler <c>Migrations/PermissionsData.json</c> icinde tanimlanir ve seeder ile yonetilir.
|
|
||||||
/// </summary>
|
|
||||||
public static class WebSiteDesign
|
|
||||||
{
|
|
||||||
public const string Home = AppCodes.Home + ".Design";
|
|
||||||
public const string About = AppCodes.About + ".Design";
|
|
||||||
public const string Services = AppCodes.Services + ".Design";
|
|
||||||
public const string Contact = AppCodes.Contact + ".Design";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Menus
|
public static class Menus
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ namespace Sozsoft.Platform.Data.Seeds;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runtime'da duzenlenebilen seed dosyalarinin klasorunu cozer.
|
/// Runtime'da duzenlenebilen seed dosyalarinin klasorunu cozer.
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Kod ile birlikte versiyonlanan sabit seed'ler (ornegin TenantDataSeeder'in okudugu
|
/// Kod ile birlikte versiyonlanan sabit seed'ler (DbMigrator projesindeki *Data.json dosyalari)
|
||||||
/// TenantData.json) buraya dahil degildir; onlar kendi projelerinin ciktisinda kalir.
|
/// buraya dahil degildir; onlar kendi projelerinin ciktisinda kalir.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Klasor bilincli olarak hicbir projenin icinde degildir: depoda <c>configs/seeds</c> altinda durur ve
|
/// Klasor bilincli olarak hicbir projenin icinde degildir: depoda <c>configs/seeds</c> altinda durur ve
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ namespace Sozsoft.Platform.Entities;
|
||||||
public class CustomComponent : FullAuditedEntity<Guid>
|
public class CustomComponent : FullAuditedEntity<Guid>
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public string RoutePath { get; set; } = string.Empty;
|
/// <summary>Rotasi olmayan bilesende (ust bilgi, alt bilgi) NULL kalir; tekil indeks dolu degerleri kapsar.</summary>
|
||||||
|
public string? RoutePath { get; set; }
|
||||||
public string Code { get; set; } = string.Empty;
|
public string Code { get; set; } = string.Empty;
|
||||||
public string? Props { get; set; }
|
public string? Props { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
@ -21,7 +22,7 @@ public class CustomComponent : FullAuditedEntity<Guid>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? DataSources { get; set; }
|
public string? DataSources { get; set; }
|
||||||
|
|
||||||
public CustomComponent(string name, string routePath, string code, string? props, string? description, bool isActive, string? dependencies, string? dataSources = null)
|
public CustomComponent(string name, string? routePath, string code, string? props, string? description, bool isActive, string? dependencies, string? dataSources = null)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
RoutePath = routePath;
|
RoutePath = routePath;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
using System;
|
|
||||||
using Volo.Abp.Domain.Entities.Auditing;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Entities;
|
|
||||||
|
|
||||||
public class About : FullAuditedEntity<Guid>
|
|
||||||
{
|
|
||||||
public string StatsJson { get; set; }
|
|
||||||
public string DescriptionsJson { get; set; }
|
|
||||||
public string SectionsJson { get; set; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
using System;
|
|
||||||
using Volo.Abp.Domain.Entities.Auditing;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Entities;
|
|
||||||
|
|
||||||
public class Contact : FullAuditedEntity<Guid>
|
|
||||||
{
|
|
||||||
public string Address { get; set; }
|
|
||||||
public string PhoneNumber { get; set; }
|
|
||||||
public string Email { get; set; }
|
|
||||||
public string Location { get; set; }
|
|
||||||
public long? TaxNumber { get; set; }
|
|
||||||
|
|
||||||
// JSON string kolonlar
|
|
||||||
public string BankJson { get; set; }
|
|
||||||
public string WorkHoursJson { get; set; }
|
|
||||||
public string MapJson { get; set; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
using System;
|
|
||||||
using Volo.Abp.Domain.Entities.Auditing;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Entities;
|
|
||||||
|
|
||||||
public class Home : FullAuditedEntity<Guid>
|
|
||||||
{
|
|
||||||
public string HeroBackgroundImageKey { get; set; }
|
|
||||||
public string HeroPrimaryCtaKey { get; set; }
|
|
||||||
public string HeroSecondaryCtaKey { get; set; }
|
|
||||||
public string FeaturesTitleKey { get; set; }
|
|
||||||
public string FeaturesSubtitleKey { get; set; }
|
|
||||||
public string SolutionsTitleKey { get; set; }
|
|
||||||
public string SolutionsSubtitleKey { get; set; }
|
|
||||||
public string CtaTitleKey { get; set; }
|
|
||||||
public string CtaSubtitleKey { get; set; }
|
|
||||||
public string CtaButtonLabelKey { get; set; }
|
|
||||||
public string SlidesJson { get; set; }
|
|
||||||
public string FeaturesJson { get; set; }
|
|
||||||
public string SolutionsJson { get; set; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
using System;
|
|
||||||
using Volo.Abp.Domain.Entities.Auditing;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Entities;
|
|
||||||
|
|
||||||
public class Service : FullAuditedEntity<Guid>
|
|
||||||
{
|
|
||||||
public string? Icon { get; set; }
|
|
||||||
public string Title { get; set; }
|
|
||||||
public string? Description { get; set; }
|
|
||||||
public string Type { get; set; }
|
|
||||||
|
|
||||||
// JSON olarak saklanacak
|
|
||||||
public string[] Features { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -89,9 +89,6 @@ public class PlatformDbContext :
|
||||||
public DbSet<IdentityLinkUser> LinkUsers { get; set; }
|
public DbSet<IdentityLinkUser> LinkUsers { get; set; }
|
||||||
public DbSet<IdentityUserDelegation> UserDelegations { get; set; }
|
public DbSet<IdentityUserDelegation> UserDelegations { get; set; }
|
||||||
public DbSet<IdentitySession> Sessions { get; set; }
|
public DbSet<IdentitySession> Sessions { get; set; }
|
||||||
public DbSet<About> Abouts { get; set; }
|
|
||||||
public DbSet<Home> Homes { get; set; }
|
|
||||||
public DbSet<Service> Services { get; set; }
|
|
||||||
public DbSet<Product> Products { get; set; }
|
public DbSet<Product> Products { get; set; }
|
||||||
public DbSet<PaymentMethod> PaymentMethods { get; set; }
|
public DbSet<PaymentMethod> PaymentMethods { get; set; }
|
||||||
public DbSet<InstallmentOption> InstallmentOptions { get; set; }
|
public DbSet<InstallmentOption> InstallmentOptions { get; set; }
|
||||||
|
|
@ -100,7 +97,6 @@ public class PlatformDbContext :
|
||||||
public DbSet<BlogPost> BlogPosts { get; set; }
|
public DbSet<BlogPost> BlogPosts { get; set; }
|
||||||
public DbSet<BlogCategory> BlogCategories { get; set; }
|
public DbSet<BlogCategory> BlogCategories { get; set; }
|
||||||
public DbSet<Demo> Demos { get; set; }
|
public DbSet<Demo> Demos { get; set; }
|
||||||
public DbSet<Contact> Contacts { get; set; }
|
|
||||||
public DbSet<Sector> Sectors { get; set; }
|
public DbSet<Sector> Sectors { get; set; }
|
||||||
public DbSet<SkillType> SkillTypes { get; set; }
|
public DbSet<SkillType> SkillTypes { get; set; }
|
||||||
public DbSet<DevRequest> DevRequests { get; set; }
|
public DbSet<DevRequest> DevRequests { get; set; }
|
||||||
|
|
@ -678,10 +674,10 @@ public class PlatformDbContext :
|
||||||
b.ConfigureByConvention();
|
b.ConfigureByConvention();
|
||||||
|
|
||||||
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||||
b.Property(x => x.RoutePath).IsRequired().HasMaxLength(512);
|
|
||||||
b.Property(x => x.Code).IsRequired();
|
b.Property(x => x.Code).IsRequired();
|
||||||
// The visual designer document contains user-facing text. Keep it Unicode so
|
// The visual designer document contains user-facing text. Keep it Unicode so
|
||||||
// Turkish and other non-ASCII property values survive a database round-trip.
|
// Turkish and other non-ASCII property values survive a database round-trip.
|
||||||
|
b.Property(x => x.RoutePath).HasMaxLength(512);
|
||||||
b.Property(x => x.Props).HasColumnType("nvarchar(max)");
|
b.Property(x => x.Props).HasColumnType("nvarchar(max)");
|
||||||
b.Property(x => x.Description).HasMaxLength(512);
|
b.Property(x => x.Description).HasMaxLength(512);
|
||||||
b.Property(x => x.Dependencies).HasMaxLength(2048);
|
b.Property(x => x.Dependencies).HasMaxLength(2048);
|
||||||
|
|
@ -689,7 +685,9 @@ public class PlatformDbContext :
|
||||||
b.Property(x => x.DataSources).HasColumnType("nvarchar(max)");
|
b.Property(x => x.DataSources).HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
|
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
|
||||||
b.HasIndex(x => new { x.RoutePath }).IsUnique().HasFilter("[IsDeleted] = 0");
|
// Rotasi olmayan bilesen (alt bilgi, ust bilgi) `RoutePath` tasimaz; tekil indeks
|
||||||
|
// yalnizca dolu degerleri kapsar, aksi halde her rotasiz bilesen digeriyle carpisir.
|
||||||
|
b.HasIndex(x => new { x.RoutePath }).IsUnique().HasFilter("[IsDeleted] = 0 AND [RoutePath] IS NOT NULL");
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Entity<ReportCategory>(b =>
|
builder.Entity<ReportCategory>(b =>
|
||||||
|
|
@ -957,64 +955,6 @@ public class PlatformDbContext :
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
});
|
});
|
||||||
|
|
||||||
//Web Site
|
|
||||||
builder.Entity<About>(b =>
|
|
||||||
{
|
|
||||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.About)), Prefix.DbSchema);
|
|
||||||
b.ConfigureByConvention();
|
|
||||||
|
|
||||||
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<Home>(b =>
|
|
||||||
{
|
|
||||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Home)), Prefix.DbSchema);
|
|
||||||
b.ConfigureByConvention();
|
|
||||||
|
|
||||||
b.Property(x => x.HeroBackgroundImageKey).HasMaxLength(256);
|
|
||||||
b.Property(x => x.HeroPrimaryCtaKey).HasMaxLength(256);
|
|
||||||
b.Property(x => x.HeroSecondaryCtaKey).HasMaxLength(256);
|
|
||||||
b.Property(x => x.FeaturesTitleKey).HasMaxLength(256);
|
|
||||||
b.Property(x => x.FeaturesSubtitleKey).HasMaxLength(256);
|
|
||||||
b.Property(x => x.SolutionsTitleKey).HasMaxLength(256);
|
|
||||||
b.Property(x => x.SolutionsSubtitleKey).HasMaxLength(256);
|
|
||||||
b.Property(x => x.CtaTitleKey).HasMaxLength(256);
|
|
||||||
b.Property(x => x.CtaSubtitleKey).HasMaxLength(256);
|
|
||||||
b.Property(x => x.CtaButtonLabelKey).HasMaxLength(256);
|
|
||||||
b.Property(x => x.SlidesJson).HasColumnType("nvarchar(max)");
|
|
||||||
b.Property(x => x.FeaturesJson).HasColumnType("nvarchar(max)");
|
|
||||||
b.Property(x => x.SolutionsJson).HasColumnType("nvarchar(max)");
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Entity<Service>(b =>
|
|
||||||
{
|
|
||||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Service)), Prefix.DbSchema);
|
|
||||||
b.ConfigureByConvention();
|
|
||||||
|
|
||||||
b.Property(x => x.Title).IsRequired().HasMaxLength(128);
|
|
||||||
b.Property(x => x.Description).HasMaxLength(512);
|
|
||||||
b.Property(x => x.Icon).HasMaxLength(64);
|
|
||||||
b.Property(x => x.Type).HasMaxLength(64);
|
|
||||||
|
|
||||||
b.Property(x => x.Features)
|
|
||||||
.HasConversion(
|
|
||||||
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
|
|
||||||
v => JsonSerializer.Deserialize<string[]>(v, (JsonSerializerOptions)null)
|
|
||||||
)
|
|
||||||
.Metadata
|
|
||||||
.SetValueComparer(
|
|
||||||
new ValueComparer<string[]>(
|
|
||||||
(a, b) => a.SequenceEqual(b),
|
|
||||||
a => a.Aggregate(0, (c, v) => HashCode.Combine(c, v.GetHashCode())),
|
|
||||||
a => a.ToArray()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
b.HasIndex(x => new { x.Title }).IsUnique().HasFilter("[IsDeleted] = 0");
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Entity<Product>(b =>
|
builder.Entity<Product>(b =>
|
||||||
{
|
{
|
||||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Product)), Prefix.DbSchema);
|
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Product)), Prefix.DbSchema);
|
||||||
|
|
@ -1165,21 +1105,6 @@ public class PlatformDbContext :
|
||||||
b.Property(x => x.Message).IsRequired().HasMaxLength(2048);
|
b.Property(x => x.Message).IsRequired().HasMaxLength(2048);
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Entity<Contact>(b =>
|
|
||||||
{
|
|
||||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Contact)), Prefix.DbSchema);
|
|
||||||
b.ConfigureByConvention();
|
|
||||||
|
|
||||||
b.Property(x => x.Address).HasMaxLength(512);
|
|
||||||
b.Property(x => x.PhoneNumber).HasMaxLength(20);
|
|
||||||
b.Property(x => x.Email).HasMaxLength(128);
|
|
||||||
b.Property(x => x.Location).HasMaxLength(256);
|
|
||||||
|
|
||||||
b.Property(x => x.BankJson).HasColumnType("nvarchar(max)");
|
|
||||||
b.Property(x => x.WorkHoursJson).HasColumnType("nvarchar(max)");
|
|
||||||
b.Property(x => x.MapJson).HasColumnType("nvarchar(max)");
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Entity<LogEntry>(b =>
|
builder.Entity<LogEntry>(b =>
|
||||||
{
|
{
|
||||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.LogEntry)), Prefix.DbSchema);
|
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.LogEntry)), Prefix.DbSchema);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260904084415_Initial")]
|
[Migration("20260908125150_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -630,55 +630,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_H_NotificationType", (string)null);
|
b.ToTable("Sas_H_NotificationType", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.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.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Sas_H_About", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Agenda", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Agenda", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -1505,74 +1456,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_H_City", (string)null);
|
b.ToTable("Sas_H_City", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Contact", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Address")
|
|
||||||
.HasMaxLength(512)
|
|
||||||
.HasColumnType("nvarchar(512)");
|
|
||||||
|
|
||||||
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(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
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(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("MapJson")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("PhoneNumber")
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("nvarchar(20)");
|
|
||||||
|
|
||||||
b.Property<long?>("TaxNumber")
|
|
||||||
.HasColumnType("bigint");
|
|
||||||
|
|
||||||
b.Property<string>("WorkHoursJson")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Sas_H_Contact", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.ContactTitle", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.ContactTitle", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -1962,7 +1845,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("RoutePath")
|
b.Property<string>("RoutePath")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(512)
|
.HasMaxLength(512)
|
||||||
.HasColumnType("nvarchar(512)");
|
.HasColumnType("nvarchar(512)");
|
||||||
|
|
||||||
|
|
@ -1974,7 +1856,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasIndex("RoutePath")
|
b.HasIndex("RoutePath")
|
||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasFilter("[IsDeleted] = 0");
|
.HasFilter("[IsDeleted] = 0 AND [RoutePath] IS NOT NULL");
|
||||||
|
|
||||||
b.ToTable("Sas_H_CustomComponent", (string)null);
|
b.ToTable("Sas_H_CustomComponent", (string)null);
|
||||||
});
|
});
|
||||||
|
|
@ -2957,95 +2839,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_T_GlobalSearch", (string)null);
|
b.ToTable("Sas_T_GlobalSearch", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Home", 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<string>("CtaButtonLabelKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("CtaSubtitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("CtaTitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("DeleterId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("DeleterId");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("DeletionTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("DeletionTime");
|
|
||||||
|
|
||||||
b.Property<string>("FeaturesJson")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("FeaturesSubtitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("FeaturesTitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("HeroBackgroundImageKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("HeroPrimaryCtaKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("HeroSecondaryCtaKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
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>("SlidesJson")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("SolutionsJson")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("SolutionsSubtitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("SolutionsTitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Sas_H_Home", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.InstallmentOption", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.InstallmentOption", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -5087,70 +4880,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Adm_T_Sector", (string)null);
|
b.ToTable("Adm_T_Sector", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Service", 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>("Description")
|
|
||||||
.HasMaxLength(512)
|
|
||||||
.HasColumnType("nvarchar(512)");
|
|
||||||
|
|
||||||
b.Property<string>("Features")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("Icon")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
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>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
b.Property<string>("Type")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("Title")
|
|
||||||
.IsUnique()
|
|
||||||
.HasFilter("[IsDeleted] = 0");
|
|
||||||
|
|
||||||
b.ToTable("Sas_H_Service", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Skill", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Skill", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
|
|
@ -922,27 +922,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table.PrimaryKey("PK_OpenIddictScopes", x => x.Id);
|
table.PrimaryKey("PK_OpenIddictScopes", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Sas_H_About",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
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_Sas_H_About", x => x.Id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Sas_H_AiBot",
|
name: "Sas_H_AiBot",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
|
@ -1059,32 +1038,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table.PrimaryKey("PK_Sas_H_ChangeLog", x => x.Id);
|
table.PrimaryKey("PK_Sas_H_ChangeLog", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Sas_H_Contact",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
Address = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
|
||||||
PhoneNumber = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
|
||||||
Email = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
|
|
||||||
Location = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
|
||||||
TaxNumber = table.Column<long>(type: "bigint", 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_Sas_H_Contact", x => x.Id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Sas_H_ContactTitle",
|
name: "Sas_H_ContactTitle",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
|
@ -1178,7 +1131,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||||
RoutePath = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
|
RoutePath = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||||
Code = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
Code = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||||
Props = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
Props = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||||
Description = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
Description = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||||
|
|
@ -1361,37 +1314,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table.PrimaryKey("PK_Sas_H_DynamicService", x => x.Id);
|
table.PrimaryKey("PK_Sas_H_DynamicService", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Sas_H_Home",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
HeroBackgroundImageKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
|
||||||
HeroPrimaryCtaKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
|
||||||
HeroSecondaryCtaKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
|
||||||
FeaturesTitleKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
|
||||||
FeaturesSubtitleKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
|
||||||
SolutionsTitleKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
|
||||||
SolutionsSubtitleKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
|
||||||
CtaTitleKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
|
||||||
CtaSubtitleKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
|
||||||
CtaButtonLabelKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
|
||||||
SlidesJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
|
||||||
FeaturesJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
|
||||||
SolutionsJson = 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_Sas_H_Home", x => x.Id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Sas_H_InstallmentOption",
|
name: "Sas_H_InstallmentOption",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
|
@ -1725,29 +1647,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table.PrimaryKey("PK_Sas_H_Route", x => x.Id);
|
table.PrimaryKey("PK_Sas_H_Route", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Sas_H_Service",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
Icon = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
|
||||||
Title = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
|
||||||
Description = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
|
||||||
Type = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
|
||||||
Features = 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_Sas_H_Service", x => x.Id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Sas_H_SettingDefinition",
|
name: "Sas_H_SettingDefinition",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
|
@ -4167,7 +4066,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table: "Sas_H_CustomComponent",
|
table: "Sas_H_CustomComponent",
|
||||||
column: "RoutePath",
|
column: "RoutePath",
|
||||||
unique: true,
|
unique: true,
|
||||||
filter: "[IsDeleted] = 0");
|
filter: "[IsDeleted] = 0 AND [RoutePath] IS NOT NULL");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sas_H_CustomEndpoint_Name",
|
name: "IX_Sas_H_CustomEndpoint_Name",
|
||||||
|
|
@ -4322,13 +4221,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
unique: true,
|
unique: true,
|
||||||
filter: "[IsDeleted] = 0");
|
filter: "[IsDeleted] = 0");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Sas_H_Service_Title",
|
|
||||||
table: "Sas_H_Service",
|
|
||||||
column: "Title",
|
|
||||||
unique: true,
|
|
||||||
filter: "[IsDeleted] = 0");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sas_H_Skill_SkillTypeId_Name",
|
name: "IX_Sas_H_Skill_SkillTypeId_Name",
|
||||||
table: "Sas_H_Skill",
|
table: "Sas_H_Skill",
|
||||||
|
|
@ -4562,9 +4454,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "OpenIddictTokens");
|
name: "OpenIddictTokens");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Sas_H_About");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_AiBot");
|
name: "Sas_H_AiBot");
|
||||||
|
|
||||||
|
|
@ -4580,9 +4469,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_ChangeLog");
|
name: "Sas_H_ChangeLog");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Sas_H_Contact");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_ContactTitle");
|
name: "Sas_H_ContactTitle");
|
||||||
|
|
||||||
|
|
@ -4616,9 +4502,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_DynamicService");
|
name: "Sas_H_DynamicService");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Sas_H_Home");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_InstallmentOption");
|
name: "Sas_H_InstallmentOption");
|
||||||
|
|
||||||
|
|
@ -4658,9 +4541,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_Route");
|
name: "Sas_H_Route");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Sas_H_Service");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_SettingDefinition");
|
name: "Sas_H_SettingDefinition");
|
||||||
|
|
||||||
|
|
@ -627,55 +627,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_H_NotificationType", (string)null);
|
b.ToTable("Sas_H_NotificationType", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.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.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Sas_H_About", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Agenda", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Agenda", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -1502,74 +1453,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_H_City", (string)null);
|
b.ToTable("Sas_H_City", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Contact", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Address")
|
|
||||||
.HasMaxLength(512)
|
|
||||||
.HasColumnType("nvarchar(512)");
|
|
||||||
|
|
||||||
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(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
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(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("MapJson")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("PhoneNumber")
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("nvarchar(20)");
|
|
||||||
|
|
||||||
b.Property<long?>("TaxNumber")
|
|
||||||
.HasColumnType("bigint");
|
|
||||||
|
|
||||||
b.Property<string>("WorkHoursJson")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Sas_H_Contact", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.ContactTitle", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.ContactTitle", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -1959,7 +1842,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("RoutePath")
|
b.Property<string>("RoutePath")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(512)
|
.HasMaxLength(512)
|
||||||
.HasColumnType("nvarchar(512)");
|
.HasColumnType("nvarchar(512)");
|
||||||
|
|
||||||
|
|
@ -1971,7 +1853,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasIndex("RoutePath")
|
b.HasIndex("RoutePath")
|
||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasFilter("[IsDeleted] = 0");
|
.HasFilter("[IsDeleted] = 0 AND [RoutePath] IS NOT NULL");
|
||||||
|
|
||||||
b.ToTable("Sas_H_CustomComponent", (string)null);
|
b.ToTable("Sas_H_CustomComponent", (string)null);
|
||||||
});
|
});
|
||||||
|
|
@ -2954,95 +2836,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Sas_T_GlobalSearch", (string)null);
|
b.ToTable("Sas_T_GlobalSearch", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Home", 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<string>("CtaButtonLabelKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("CtaSubtitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("CtaTitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("DeleterId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("DeleterId");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("DeletionTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("DeletionTime");
|
|
||||||
|
|
||||||
b.Property<string>("FeaturesJson")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("FeaturesSubtitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("FeaturesTitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("HeroBackgroundImageKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("HeroPrimaryCtaKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("HeroSecondaryCtaKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
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>("SlidesJson")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("SolutionsJson")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("SolutionsSubtitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<string>("SolutionsTitleKey")
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Sas_H_Home", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.InstallmentOption", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.InstallmentOption", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -5084,70 +4877,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.ToTable("Adm_T_Sector", (string)null);
|
b.ToTable("Adm_T_Sector", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Service", 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>("Description")
|
|
||||||
.HasMaxLength(512)
|
|
||||||
.HasColumnType("nvarchar(512)");
|
|
||||||
|
|
||||||
b.Property<string>("Features")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("Icon")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
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>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
b.Property<string>("Type")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("Title")
|
|
||||||
.IsUnique()
|
|
||||||
.HasFilter("[IsDeleted] = 0");
|
|
||||||
|
|
||||||
b.ToTable("Sas_H_Service", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Skill", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Skill", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
@ -1,360 +0,0 @@
|
||||||
{
|
|
||||||
"Homes": [
|
|
||||||
{
|
|
||||||
"heroBackgroundImageKey": "Public.home.hero.backgroundImage",
|
|
||||||
"heroPrimaryCtaKey": "App.HeroCta.Consultation",
|
|
||||||
"heroSecondaryCtaKey": "App.HeroCta.Discover",
|
|
||||||
"featuresTitleKey": "App.PublicFeatures.FeaturesTitle",
|
|
||||||
"featuresSubtitleKey": "App.PublicFeatures.Subtitle",
|
|
||||||
"solutionsTitleKey": "App.PublicSolutions.SolutionsTitle",
|
|
||||||
"solutionsSubtitleKey": "App.PublicSolutions.Subtitle",
|
|
||||||
"ctaTitleKey": "App.PublicCommon.GetStarted",
|
|
||||||
"ctaSubtitleKey": "App.PublicCommon.Contact",
|
|
||||||
"ctaButtonLabelKey": "App.PublicCommon.LearnMore",
|
|
||||||
"slides": [
|
|
||||||
{
|
|
||||||
"titleKey": "App.HeroSlide1.Slide1Title",
|
|
||||||
"subtitleKey": "App.HeroSlide1.Subtitle",
|
|
||||||
"services": [
|
|
||||||
{
|
|
||||||
"icon": "FaCalendarAlt",
|
|
||||||
"titleKey": "App.Slide1Service1.Service1Title",
|
|
||||||
"descriptionKey": "App.Slide1Service1.Service1Desc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaUsers",
|
|
||||||
"titleKey": "App.Slide1Service2.Service2Title",
|
|
||||||
"descriptionKey": "App.Slide1Service2.Service2Desc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaShieldAlt",
|
|
||||||
"titleKey": "App.Slide1Service3.Service3Title",
|
|
||||||
"descriptionKey": "App.Slide1Service3.Service3Desc"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"titleKey": "App.HeroSlide2.Slide2Title",
|
|
||||||
"subtitleKey": "App.HeroSlide2.Subtitle",
|
|
||||||
"services": [
|
|
||||||
{
|
|
||||||
"icon": "FaChartBar",
|
|
||||||
"titleKey": "App.Slide2Service1.Service1Title",
|
|
||||||
"descriptionKey": "App.Slide2Service1.Service1Desc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaCreditCard",
|
|
||||||
"titleKey": "App.Slide2Service2.Service2Title",
|
|
||||||
"descriptionKey": "App.Slide2Service2.Service2Desc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaDatabase",
|
|
||||||
"titleKey": "App.Slide2Service3.Service3Title",
|
|
||||||
"descriptionKey": "App.Slide2Service3.Service3Desc"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"titleKey": "App.HeroSlide3.Slide3Title",
|
|
||||||
"subtitleKey": "App.HeroSlide3.Subtitle",
|
|
||||||
"services": [
|
|
||||||
{
|
|
||||||
"icon": "FaDesktop",
|
|
||||||
"titleKey": "App.Slide3Service1.Service1Title",
|
|
||||||
"descriptionKey": "App.Slide3Service1.Service1Desc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaServer",
|
|
||||||
"titleKey": "App.Slide3Service2.Service2Title",
|
|
||||||
"descriptionKey": "App.Slide3Service2.Service2Desc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaMobileAlt",
|
|
||||||
"titleKey": "App.Slide3Service3.Service3Title",
|
|
||||||
"descriptionKey": "App.Slide3Service3.Service3Desc"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"features": [
|
|
||||||
{
|
|
||||||
"icon": "FaUsers",
|
|
||||||
"titleKey": "App.PublicFeatures.Reliable",
|
|
||||||
"descriptionKey": "App.FeaturesReliable.ReliableDesc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaCalendarAlt",
|
|
||||||
"titleKey": "App.VideoRoom.Planning",
|
|
||||||
"descriptionKey": "App.FeaturesRapid.RapidDesc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaBookOpen",
|
|
||||||
"titleKey": "App.PublicFeatures.Expert",
|
|
||||||
"descriptionKey": "App.FeaturesExpert.ExpertDesc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaCreditCard",
|
|
||||||
"titleKey": "App.PublicFeatures.Muhasebe",
|
|
||||||
"descriptionKey": "App.FeaturesMuhasebe.MuhasebeDesc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaRegComment",
|
|
||||||
"titleKey": "App.PublicFeatures.Iletisim",
|
|
||||||
"descriptionKey": "App.FeaturesIletisim.IletisimDesc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaPhone",
|
|
||||||
"titleKey": "App.PublicFeatures.Mobil",
|
|
||||||
"descriptionKey": "App.FeaturesMobil.MobilDesc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaChartBar",
|
|
||||||
"titleKey": "App.PublicFeatures.Scalable",
|
|
||||||
"descriptionKey": "App.FeaturesScalable.ScalableDesc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaShieldAlt",
|
|
||||||
"titleKey": "App.Listform.ListformField.Security",
|
|
||||||
"descriptionKey": "App.FeaturesGuvenlik.GuvenlikDesc"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"solutions": [
|
|
||||||
{
|
|
||||||
"icon": "FaDesktop",
|
|
||||||
"colorClass": "bg-blue-600",
|
|
||||||
"titleKey": "App.BlogCategories.Webdev",
|
|
||||||
"descriptionKey": "App.SolutionsWeb.WebDesc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaMobileAlt",
|
|
||||||
"colorClass": "bg-purple-600",
|
|
||||||
"titleKey": "App.ServicesMobile.MobileTitle",
|
|
||||||
"descriptionKey": "App.SolutionsMobile.MobileDesc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaServer",
|
|
||||||
"colorClass": "bg-green-600",
|
|
||||||
"titleKey": "App.SolutionsCustom.CustomTitle",
|
|
||||||
"descriptionKey": "App.SolutionsCustom.CustomDesc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaDatabase",
|
|
||||||
"colorClass": "bg-red-600",
|
|
||||||
"titleKey": "App.SolutionsDatabase.DatabaseTitle",
|
|
||||||
"descriptionKey": "App.SolutionsDatabase.DatabaseDesc"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Abouts": [
|
|
||||||
{
|
|
||||||
"stats": [
|
|
||||||
{
|
|
||||||
"Icon": "FaUser",
|
|
||||||
"Value": "740\u002B",
|
|
||||||
"LabelKey": "App.AboutStats.Users",
|
|
||||||
"UseCounter": true,
|
|
||||||
"CounterEnd": "740\u002B",
|
|
||||||
"CounterSuffix": "\u002B",
|
|
||||||
"CounterDuration": 3000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaUsers",
|
|
||||||
"value": "310+",
|
|
||||||
"labelKey": "App.AboutStats.Clients",
|
|
||||||
"useCounter": true,
|
|
||||||
"counterEnd": "310+",
|
|
||||||
"counterSuffix": "+",
|
|
||||||
"counterDuration": 2500
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaAward",
|
|
||||||
"value": "25",
|
|
||||||
"labelKey": "App.AboutStats.Experience",
|
|
||||||
"useCounter": true,
|
|
||||||
"counterEnd": "20",
|
|
||||||
"counterSuffix": "+",
|
|
||||||
"counterDuration": 2000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaClock",
|
|
||||||
"value": "7/24",
|
|
||||||
"labelKey": "App.AboutStats.Support",
|
|
||||||
"useCounter": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaGlobe",
|
|
||||||
"value": "3",
|
|
||||||
"labelKey": "App.Definitions.Country",
|
|
||||||
"useCounter": true,
|
|
||||||
"counterEnd": "3",
|
|
||||||
"counterDuration": 1500
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"descriptions": [
|
|
||||||
"App.AboutDescription.Part1",
|
|
||||||
"App.AboutDescription.Motto",
|
|
||||||
"App.AboutDescription.Part2",
|
|
||||||
"App.AboutDescription.Closing"
|
|
||||||
],
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"key": "App.PublicAbout.Mission",
|
|
||||||
"descKey": "App.AboutMission.MissionDesc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "App.PublicAbout.Vision",
|
|
||||||
"descKey": "App.AboutVision.VisionDesc"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Services": [
|
|
||||||
{
|
|
||||||
"icon": "FaCode",
|
|
||||||
"title": "App.ServicesSoftware.SoftwareTitle",
|
|
||||||
"description": "App.ServicesSoftware.SoftwareDesc",
|
|
||||||
"type": "service",
|
|
||||||
"features": [
|
|
||||||
"App.SoftwareFeatures.Analysis",
|
|
||||||
"App.SoftwareFeatures.Design",
|
|
||||||
"App.SoftwareFeatures.Development",
|
|
||||||
"App.SoftwareFeatures.Testing",
|
|
||||||
"App.SoftwareFeatures.Maintenance"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaUsers",
|
|
||||||
"title": "App.BlogCategories.Webdev",
|
|
||||||
"description": "App.ServicesWeb.WebDesc",
|
|
||||||
"type": "service",
|
|
||||||
"features": [
|
|
||||||
"App.WebFeatures.Frontend",
|
|
||||||
"App.WebFeatures.Backend",
|
|
||||||
"App.WebFeatures.Api",
|
|
||||||
"App.WebFeatures.Seo",
|
|
||||||
"App.WebFeatures.Performance"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaShieldAlt",
|
|
||||||
"title": "App.ServicesMobile.MobileTitle",
|
|
||||||
"description": "App.ServicesMobile.MobileDesc",
|
|
||||||
"type": "service",
|
|
||||||
"features": [
|
|
||||||
"App.MobileFeatures.Design",
|
|
||||||
"App.MobileFeatures.Native",
|
|
||||||
"App.MobileFeatures.Cross",
|
|
||||||
"App.MobileFeatures.Push",
|
|
||||||
"App.MobileFeatures.Store"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaServer",
|
|
||||||
"title": "App.ServicesDatabase.DatabaseTitle",
|
|
||||||
"description": "App.ServicesDatabase.DatabaseDesc",
|
|
||||||
"type": "service",
|
|
||||||
"features": [
|
|
||||||
"App.DatabaseFeatures.Design",
|
|
||||||
"App.DatabaseFeatures.Optimization",
|
|
||||||
"App.DatabaseFeatures.Migration",
|
|
||||||
"App.DatabaseFeatures.Backup",
|
|
||||||
"App.DatabaseFeatures.Recovery"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaGlobe",
|
|
||||||
"title": "App.ServicesIntegration.IntegrationTitle",
|
|
||||||
"description": "App.ServicesIntegration.IntegrationDesc",
|
|
||||||
"type": "service",
|
|
||||||
"features": [
|
|
||||||
"App.IntegrationFeatures.Api",
|
|
||||||
"App.IntegrationFeatures.Middleware",
|
|
||||||
"App.IntegrationFeatures.Legacy",
|
|
||||||
"App.IntegrationFeatures.Realtime",
|
|
||||||
"App.IntegrationFeatures.Monitoring"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaCog",
|
|
||||||
"title": "App.ServicesConsulting.ConsultingTitle",
|
|
||||||
"description": "App.ServicesConsulting.ConsultingDesc",
|
|
||||||
"type": "service",
|
|
||||||
"features": [
|
|
||||||
"App.ConsultingFeatures.Tech",
|
|
||||||
"App.ConsultingFeatures.Project",
|
|
||||||
"App.ConsultingFeatures.Digital",
|
|
||||||
"App.ConsultingFeatures.Risk",
|
|
||||||
"App.ConsultingFeatures.Training"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaUsers",
|
|
||||||
"title": "App.SupportBranchRemote.BranchRemoteTitle",
|
|
||||||
"description": null,
|
|
||||||
"type": "support",
|
|
||||||
"features": [
|
|
||||||
"App.BranchRemoteFeatures.Priority",
|
|
||||||
"App.BranchRemoteFeatures.Remote",
|
|
||||||
"App.BranchRemoteFeatures.Optimization",
|
|
||||||
"App.BranchRemoteFeatures.Maintenance",
|
|
||||||
"App.BranchRemoteFeatures.Consulting"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaServer",
|
|
||||||
"title": "App.SupportBackup.BackupTitle",
|
|
||||||
"description": null,
|
|
||||||
"type": "support",
|
|
||||||
"features": [
|
|
||||||
"App.BackupFeatures.Daily",
|
|
||||||
"App.BackupFeatures.Encrypted",
|
|
||||||
"App.BackupFeatures.Recovery",
|
|
||||||
"App.BackupFeatures.Verification",
|
|
||||||
"App.BackupFeatures.Access"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "FaGlobe",
|
|
||||||
"title": "App.SupportSms.SmsTitle",
|
|
||||||
"description": null,
|
|
||||||
"type": "support",
|
|
||||||
"features": [
|
|
||||||
"App.SmsFeatures.Packages",
|
|
||||||
"App.SmsFeatures.Bulk",
|
|
||||||
"App.SmsFeatures.Template",
|
|
||||||
"App.SmsFeatures.Reporting",
|
|
||||||
"App.SmsFeatures.Api"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Contacts": [
|
|
||||||
{
|
|
||||||
"address": "App.ContactAddress.Full",
|
|
||||||
"phoneNumber": "5447697638",
|
|
||||||
"email": "destek@sozsoft.com",
|
|
||||||
"location": "Sarıgazi",
|
|
||||||
"taxNumber": "32374982750",
|
|
||||||
"bank": {
|
|
||||||
"accountHolder": "Özlem Öztürk",
|
|
||||||
"branch": "03663 / Enpara",
|
|
||||||
"accountNumber": "73941177",
|
|
||||||
"iban": "TR11 0015 7000 0000 0073 9411 77"
|
|
||||||
},
|
|
||||||
"workHour": {
|
|
||||||
"weekday": "App.ContactWorkHours.Weekday",
|
|
||||||
"weekend": "App.ContactWorkHours.Weekend",
|
|
||||||
"whatsapp": "App.ContactWorkHours.Whatsapp"
|
|
||||||
},
|
|
||||||
"map": {
|
|
||||||
"title": "App.PublicContact.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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Sozsoft.Platform.Entities;
|
|
||||||
using Sozsoft.Platform.Enums;
|
|
||||||
using Sozsoft.Platform.Forum;
|
|
||||||
using Sozsoft.Platform.Public;
|
|
||||||
using Volo.Abp.Data;
|
|
||||||
using Volo.Abp.DependencyInjection;
|
|
||||||
using Volo.Abp.Domain.Repositories;
|
|
||||||
using Volo.Abp.Identity;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Data.Seeds;
|
|
||||||
|
|
||||||
public class TenantSeederDto
|
|
||||||
{
|
|
||||||
//Saas
|
|
||||||
|
|
||||||
//Definitions
|
|
||||||
|
|
||||||
//Public
|
|
||||||
public List<HomeSeedDto> Homes { get; set; }
|
|
||||||
public List<AboutSeedDto> Abouts { get; set; }
|
|
||||||
public List<ServiceSeedDto> Services { get; set; }
|
|
||||||
public List<ContactSeedDto> Contacts { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
//Forum
|
|
||||||
|
|
||||||
//Intranet
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ServiceSeedDto
|
|
||||||
{
|
|
||||||
public string Icon { get; set; }
|
|
||||||
public string Title { get; set; }
|
|
||||||
public string Description { get; set; }
|
|
||||||
public string Type { 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 HomeSeedDto
|
|
||||||
{
|
|
||||||
public string HeroBackgroundImageKey { get; set; }
|
|
||||||
public string HeroPrimaryCtaKey { get; set; }
|
|
||||||
public string HeroSecondaryCtaKey { get; set; }
|
|
||||||
public string FeaturesTitleKey { get; set; }
|
|
||||||
public string FeaturesSubtitleKey { get; set; }
|
|
||||||
public string SolutionsTitleKey { get; set; }
|
|
||||||
public string SolutionsSubtitleKey { get; set; }
|
|
||||||
public string CtaTitleKey { get; set; }
|
|
||||||
public string CtaSubtitleKey { get; set; }
|
|
||||||
public string CtaButtonLabelKey { get; set; }
|
|
||||||
public List<HomeSlideSeedDto> Slides { get; set; }
|
|
||||||
public List<HomeFeatureSeedDto> Features { get; set; }
|
|
||||||
public List<HomeSolutionSeedDto> Solutions { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HomeSlideSeedDto
|
|
||||||
{
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string SubtitleKey { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
public List<HomeSlideServiceSeedDto> Services { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HomeSlideServiceSeedDto
|
|
||||||
{
|
|
||||||
public string Icon { get; set; }
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string DescriptionKey { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HomeFeatureSeedDto
|
|
||||||
{
|
|
||||||
public string Icon { get; set; }
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string DescriptionKey { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HomeSolutionSeedDto
|
|
||||||
{
|
|
||||||
public string Icon { get; set; }
|
|
||||||
public string ColorClass { get; set; }
|
|
||||||
public string TitleKey { get; set; }
|
|
||||||
public string DescriptionKey { get; set; }
|
|
||||||
public string StyleClass { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ContactSeedDto
|
|
||||||
{
|
|
||||||
public string Address { get; set; }
|
|
||||||
public string PhoneNumber { get; set; }
|
|
||||||
public string Email { get; set; }
|
|
||||||
public string Location { get; set; }
|
|
||||||
public long TaxNumber { get; set; }
|
|
||||||
public BankDto Bank { get; set; }
|
|
||||||
public WorkHoursDto WorkHour { get; set; }
|
|
||||||
public MapDto Map { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
|
|
||||||
{
|
|
||||||
private readonly ILookupNormalizer _lookupNormalizer;
|
|
||||||
private readonly IRepository<Branch, Guid> _branchRepository;
|
|
||||||
private readonly IRepository<Home, Guid> _homeRepository;
|
|
||||||
private readonly IRepository<About, Guid> _aboutRepository;
|
|
||||||
private readonly IRepository<Service, Guid> _servicesRepository;
|
|
||||||
private readonly IRepository<Contact, Guid> _contactRepository;
|
|
||||||
private readonly IIdentityUserRepository _repositoryUser;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public TenantDataSeeder(
|
|
||||||
ILookupNormalizer lookupNormalizer,
|
|
||||||
IIdentityUserRepository repositoryUser,
|
|
||||||
IRepository<Branch, Guid> branchRepository,
|
|
||||||
IRepository<Home, Guid> homeRepository,
|
|
||||||
IRepository<About, Guid> aboutRepository,
|
|
||||||
IRepository<Service, Guid> servicesRepository,
|
|
||||||
IRepository<Contact, Guid> contactRepository
|
|
||||||
|
|
||||||
)
|
|
||||||
{
|
|
||||||
_lookupNormalizer = lookupNormalizer;
|
|
||||||
_repositoryUser = repositoryUser;
|
|
||||||
_branchRepository = branchRepository;
|
|
||||||
_homeRepository = homeRepository;
|
|
||||||
_servicesRepository = servicesRepository;
|
|
||||||
_aboutRepository = aboutRepository;
|
|
||||||
_contactRepository = contactRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SeedAsync(DataSeedContext context)
|
|
||||||
{
|
|
||||||
var assemblyLocation = Path.GetDirectoryName(typeof(TenantDataSeeder).Assembly.Location)!;
|
|
||||||
|
|
||||||
var configuration = new ConfigurationBuilder()
|
|
||||||
.SetBasePath(assemblyLocation)
|
|
||||||
.AddJsonFile(Path.Combine("Seeds", "TenantData.json"), optional: false, reloadOnChange: false)
|
|
||||||
.AddJsonFile(Path.Combine("Seeds", $"TenantData.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? ""}.json"), optional: true)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var items = configuration.Get<TenantSeederDto>();
|
|
||||||
|
|
||||||
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
|
||||||
|
|
||||||
foreach (var item in items.Services)
|
|
||||||
{
|
|
||||||
var exists = await _servicesRepository.AnyAsync(x => x.Title == item.Title);
|
|
||||||
|
|
||||||
if (!exists)
|
|
||||||
{
|
|
||||||
await _servicesRepository.InsertAsync(new Service
|
|
||||||
{
|
|
||||||
Icon = item.Icon,
|
|
||||||
Title = item.Title,
|
|
||||||
Description = item.Description,
|
|
||||||
Type = item.Type,
|
|
||||||
Features = item.Features
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in items.Abouts)
|
|
||||||
{
|
|
||||||
var exists = await _aboutRepository.FirstOrDefaultAsync();
|
|
||||||
if (exists == null)
|
|
||||||
{
|
|
||||||
await _aboutRepository.InsertAsync(new About
|
|
||||||
{
|
|
||||||
StatsJson = JsonSerializer.Serialize(item.Stats),
|
|
||||||
DescriptionsJson = JsonSerializer.Serialize(item.Descriptions),
|
|
||||||
SectionsJson = JsonSerializer.Serialize(item.Sections)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in items.Homes)
|
|
||||||
{
|
|
||||||
var exists = await _homeRepository.FirstOrDefaultAsync();
|
|
||||||
if (exists == null)
|
|
||||||
{
|
|
||||||
await _homeRepository.InsertAsync(new Home
|
|
||||||
{
|
|
||||||
HeroBackgroundImageKey = item.HeroBackgroundImageKey,
|
|
||||||
HeroPrimaryCtaKey = item.HeroPrimaryCtaKey,
|
|
||||||
HeroSecondaryCtaKey = item.HeroSecondaryCtaKey,
|
|
||||||
FeaturesTitleKey = item.FeaturesTitleKey,
|
|
||||||
FeaturesSubtitleKey = item.FeaturesSubtitleKey,
|
|
||||||
SolutionsTitleKey = item.SolutionsTitleKey,
|
|
||||||
SolutionsSubtitleKey = item.SolutionsSubtitleKey,
|
|
||||||
CtaTitleKey = item.CtaTitleKey,
|
|
||||||
CtaSubtitleKey = item.CtaSubtitleKey,
|
|
||||||
CtaButtonLabelKey = item.CtaButtonLabelKey,
|
|
||||||
SlidesJson = JsonSerializer.Serialize(item.Slides),
|
|
||||||
FeaturesJson = JsonSerializer.Serialize(item.Features),
|
|
||||||
SolutionsJson = JsonSerializer.Serialize(item.Solutions)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in items.Contacts)
|
|
||||||
{
|
|
||||||
var exists = await _contactRepository.FirstOrDefaultAsync();
|
|
||||||
if (exists == null)
|
|
||||||
{
|
|
||||||
await _contactRepository.InsertAsync(new Contact
|
|
||||||
{
|
|
||||||
Address = item.Address,
|
|
||||||
PhoneNumber = item.PhoneNumber,
|
|
||||||
Email = item.Email,
|
|
||||||
Location = item.Location,
|
|
||||||
TaxNumber = item.TaxNumber,
|
|
||||||
BankJson = JsonSerializer.Serialize(item.Bank),
|
|
||||||
WorkHoursJson = JsonSerializer.Serialize(item.WorkHour),
|
|
||||||
MapJson = JsonSerializer.Serialize(item.Map)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,22 +7,6 @@
|
||||||
<RootNamespace>Sozsoft.Platform</RootNamespace>
|
<RootNamespace>Sozsoft.Platform</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Remove="Seeds\TenantData.json" />
|
|
||||||
<Content Include="Seeds\TenantData.json">
|
|
||||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="Seeds\TenantData.Dev.json">
|
|
||||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="Seeds\TenantData.Production.json">
|
|
||||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="EFCore.BulkExtensions" Version="10.0.1" />
|
<PackageReference Include="EFCore.BulkExtensions" Version="10.0.1" />
|
||||||
<ProjectReference Include="..\..\modules\Sozsoft.Languages\Sozsoft.Languages.EntityFrameworkCore\Sozsoft.Languages.EntityFrameworkCore.csproj" />
|
<ProjectReference Include="..\..\modules\Sozsoft.Languages\Sozsoft.Languages.EntityFrameworkCore\Sozsoft.Languages.EntityFrameworkCore.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,28 @@ public class PlatformApplicationConfigurationContributor : IApplicationConfigura
|
||||||
context.ApplicationConfiguration.SetProperty(
|
context.ApplicationConfiguration.SetProperty(
|
||||||
"customComponents", await customComponentRepository.GetListAsync());
|
"customComponents", await customComponentRepository.GetListAsync());
|
||||||
|
|
||||||
|
// Public site menusu: ziyaretci menu ucunu cagiramaz ([Authorize]), bu yuzden
|
||||||
|
// rotalar ve custom component'ler gibi anonim erisilen konfigurasyonla tasinir.
|
||||||
|
var menuRepository = context.ServiceProvider.GetRequiredService<IRepository<Menu, Guid>>();
|
||||||
|
var publicMenus = await menuRepository.GetListAsync(
|
||||||
|
menu => menu.ModuleId == PlatformConsts.PublicSite.ModuleId && !menu.IsDisabled);
|
||||||
|
|
||||||
|
context.ApplicationConfiguration.SetProperty("publicMenus", publicMenus
|
||||||
|
.OrderBy(menu => menu.Order)
|
||||||
|
.Select(menu => new
|
||||||
|
{
|
||||||
|
code = menu.Code,
|
||||||
|
displayName = menu.DisplayName,
|
||||||
|
url = menu.Url,
|
||||||
|
icon = menu.Icon,
|
||||||
|
order = menu.Order,
|
||||||
|
parentCode = menu.ParentCode,
|
||||||
|
cssClass = menu.CssClass,
|
||||||
|
target = menu.Target,
|
||||||
|
requiredPermissionName = menu.RequiredPermissionName
|
||||||
|
})
|
||||||
|
.ToList());
|
||||||
|
|
||||||
var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
|
var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
|
||||||
if (currentUser.Id.HasValue)
|
if (currentUser.Id.HasValue)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
21
claude.md
21
claude.md
|
|
@ -184,6 +184,10 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
||||||
`permission.constant.ts` + `LanguagesData.json`.
|
`permission.constant.ts` + `LanguagesData.json`.
|
||||||
- **Yeni bir dil anahtarı** → `LanguagesData.json`; eklemeden önce anahtarın zaten var olup
|
- **Yeni bir dil anahtarı** → `LanguagesData.json`; eklemeden önce anahtarın zaten var olup
|
||||||
olmadığını kontrol et.
|
olmadığını kontrol et.
|
||||||
|
- **Custom component sınıfları** → bileşen kodu veritabanında yaşar, Tailwind derlemede yalnızca
|
||||||
|
seed dosyasını görür. Seed dosyası `ui/` dışında olduğu için aynı sınıflar `ui/safelist.txt`
|
||||||
|
sonuna da yazılır (`twSafelistGenerator`, derlemede otomatik); yoksa uygulama sunucuda stilsiz
|
||||||
|
kalır. İkinci bir safelist dosyası açma.
|
||||||
- **Custom component kaydı/silinmesi** → `data/App.DeveloperKit.CustomComponents.json`
|
- **Custom component kaydı/silinmesi** → `data/App.DeveloperKit.CustomComponents.json`
|
||||||
(Custom Components ekranının seed verisi); bileşenin kendi dosyası yoktur, her bileşen bu
|
(Custom Components ekranının seed verisi); bileşenin kendi dosyası yoktur, her bileşen bu
|
||||||
dosyada bir satırdır. Hem listeden hem Visual Designer'dan (`CustomComponentAppService`) yapılan
|
dosyada bir satırdır. Hem listeden hem Visual Designer'dan (`CustomComponentAppService`) yapılan
|
||||||
|
|
@ -234,6 +238,23 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
||||||
kullanıcının runtime revizyonunu ezmez. Yeni bir seeder yazarken bu kuralı taklit et: **migrate
|
kullanıcının runtime revizyonunu ezmez. Yeni bir seeder yazarken bu kuralı taklit et: **migrate
|
||||||
varsayılanı eklemektir, ezmek değil.**
|
varsayılanı eklemektir, ezmek değil.**
|
||||||
- **`MenusData.json`** → `Routes` bölümüne dokunma; yalnızca `Modules` ve `Menus`.
|
- **`MenusData.json`** → `Routes` bölümüne dokunma; yalnızca `Modules` ve `Menus`.
|
||||||
|
- **Public site** → menü `Menu` tablosunda `ModuleId = "Pub"` altında (`PlatformConsts.PublicSite`);
|
||||||
|
`App.Public.Header` hem üst menüyü hem alt bilgideki hızlı bağlantıları besler (ikisi de
|
||||||
|
`PublicNav` düğümü) ve ziyaretçiye uygulama konfigürasyonunun `publicMenus` alanıyla taşınır
|
||||||
|
(menü ucu `[Authorize]`). Sayfa ise Custom Component'tir: `/admin/` ile
|
||||||
|
başlamayan `RoutePath` otomatik public route olur ve aynı yolu paylaşan fiziksel route'u gölgeler;
|
||||||
|
`RoutePath` boş bırakılabilir (rotasız bileşen). Yeni public sayfa =
|
||||||
|
`data/App.DeveloperKit.CustomComponents.json` satırı + `Pub` menü kaydı; ikisi birlikte yazılır.
|
||||||
|
Üst ve alt bilgi rotasız bileşenlerdir (`PublicHeader`, `PublicFooter`), layout ikisini de
|
||||||
|
`renderComponent` ile çizer; rotasız bileşende `RoutePath` **NULL** yazılır (tekil indeks yalnızca
|
||||||
|
dolu değerleri kapsar). Üst bilginin davranışı `components/publicSite` katalog düğümlerinde,
|
||||||
|
yerleşimi tasarımcıdadır.
|
||||||
|
**Public bileşenlerin hepsi tasarımcı belgesidir** (`Props.visualDesigner`, `sourceMode: visual`);
|
||||||
|
kod belgeden üretilir (`generateDesignerCode`), elle yazılmış koda dönülmez. Runtime kapsamında
|
||||||
|
`react-icons`/`react-router` yoktur — ikon `PlatformIcon` düğümü, gezinme `a` düğümü. Kanvasın
|
||||||
|
doğrudan çizdiği katalog düğümleri tek sözlüktedir (`componentEditor/runtimeNodes.ts`); yeni bir
|
||||||
|
düğüm eklerken bileşen + bu sözlük + `catalog.ts` tanımı + dil anahtarı birlikte yazılır. Elle
|
||||||
|
yazılmış bir bileşen tasarımcıda kod modunda açılır, kanvasa çevrilmez. Ayrıntı: `README.md` §10.4.
|
||||||
- **Çok değerli alan (`dxTagBox` / çoklu `dxGridBox`)** → tek metin kolonunda `|` ile saklanır.
|
- **Çok değerli alan (`dxTagBox` / çoklu `dxGridBox`)** → tek metin kolonunda `|` ile saklanır.
|
||||||
Ekran varsayılan `list-form-data/*` ucundan kaydediyorsa birleştirmeyi `QueryHelper` yapar;
|
Ekran varsayılan `list-form-data/*` ucundan kaydediyorsa birleştirmeyi `QueryHelper` yapar;
|
||||||
tipli DTO alan bir uca (`list-form-dynamic-api/...`, Custom Endpoint, Dynamic Service)
|
tipli DTO alan bir uca (`list-form-dynamic-api/...`, Custom Endpoint, Dynamic Service)
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -47,28 +47,6 @@
|
||||||
"IsQuantityBased": true,
|
"IsQuantityBased": true,
|
||||||
"ImageUrl": "https://images.pexels.com/photos/3184360/pexels-photo-3184360.jpeg?auto=compress&cs=tinysrgb&w=300"
|
"ImageUrl": "https://images.pexels.com/photos/3184360/pexels-photo-3184360.jpeg?auto=compress&cs=tinysrgb&w=300"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"Id": "c2cf95b8-f63b-5ad4-9e5e-edbb8901da82",
|
|
||||||
"Name": "App.PublicProducts.TeacherLicense",
|
|
||||||
"Description": "App.ProductsTeacherLicense.TeacherLicenseDesc",
|
|
||||||
"Category": "App.ProductsCategories.Lisans",
|
|
||||||
"Order": 5,
|
|
||||||
"MonthlyPrice": 500,
|
|
||||||
"YearlyPrice": 1700,
|
|
||||||
"IsQuantityBased": true,
|
|
||||||
"ImageUrl": "https://images.pexels.com/photos/5212345/pexels-photo-5212345.jpeg?auto=compress&cs=tinysrgb&w=300"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "8c231f32-2709-54f7-8607-35e63e9b4a4e",
|
|
||||||
"Name": "App.PublicProducts.MobileReporting",
|
|
||||||
"Description": "App.ProductsMobileReporting.MobileReportingDesc",
|
|
||||||
"Category": "App.ProductsCategories.Lisans",
|
|
||||||
"Order": 6,
|
|
||||||
"MonthlyPrice": 400,
|
|
||||||
"YearlyPrice": 1400,
|
|
||||||
"IsQuantityBased": false,
|
|
||||||
"ImageUrl": "https://images.pexels.com/photos/1092644/pexels-photo-1092644.jpeg?auto=compress&cs=tinysrgb&w=300"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Id": "0c927e71-0c51-5a2d-899c-c22302b563a0",
|
"Id": "0c927e71-0c51-5a2d-899c-c22302b563a0",
|
||||||
"Name": "App.PublicProducts.RemoteBranchTraining",
|
"Name": "App.PublicProducts.RemoteBranchTraining",
|
||||||
|
|
|
||||||
291
ui/safelist.txt
291
ui/safelist.txt
|
|
@ -4495,4 +4495,293 @@ mr-3.5
|
||||||
mr-1.2
|
mr-1.2
|
||||||
mr-1.3
|
mr-1.3
|
||||||
mr-1.4
|
mr-1.4
|
||||||
sozsoft-*
|
sozsoft-*
|
||||||
|
# --- custom component classes ---
|
||||||
|
!bg-transparent
|
||||||
|
absolute
|
||||||
|
backdrop-blur-sm
|
||||||
|
bg-amber-50
|
||||||
|
bg-blue-600
|
||||||
|
bg-blue-900
|
||||||
|
bg-blue-950/25
|
||||||
|
bg-gradient-to-br
|
||||||
|
bg-gradient-to-r
|
||||||
|
bg-gray-100
|
||||||
|
bg-gray-50
|
||||||
|
bg-gray-950
|
||||||
|
bg-gray-950/80
|
||||||
|
bg-green-600
|
||||||
|
bg-purple-600
|
||||||
|
bg-red-50
|
||||||
|
bg-red-600
|
||||||
|
bg-sky-600
|
||||||
|
bg-slate-100
|
||||||
|
bg-slate-300
|
||||||
|
bg-slate-50
|
||||||
|
bg-white
|
||||||
|
bg-white/10
|
||||||
|
bg-white/20
|
||||||
|
bg-white/5
|
||||||
|
block
|
||||||
|
border
|
||||||
|
border-0
|
||||||
|
border-amber-300
|
||||||
|
border-b
|
||||||
|
border-blue-600
|
||||||
|
border-collapse
|
||||||
|
border-dashed
|
||||||
|
border-gray-200
|
||||||
|
border-l-4
|
||||||
|
border-slate-100
|
||||||
|
border-slate-200
|
||||||
|
border-slate-300
|
||||||
|
border-t
|
||||||
|
border-white/10
|
||||||
|
border-white/5
|
||||||
|
break-all
|
||||||
|
col-start-3
|
||||||
|
container
|
||||||
|
dark:bg-amber-950
|
||||||
|
dark:bg-gray-800
|
||||||
|
dark:bg-gray-900
|
||||||
|
dark:bg-gray-950
|
||||||
|
dark:bg-gray-950/45
|
||||||
|
dark:bg-red-950
|
||||||
|
dark:bg-slate-700
|
||||||
|
dark:bg-slate-800
|
||||||
|
dark:bg-slate-900
|
||||||
|
dark:block
|
||||||
|
dark:border-amber-800
|
||||||
|
dark:border-gray-800
|
||||||
|
dark:border-slate-700
|
||||||
|
dark:border-slate-800
|
||||||
|
dark:from-gray-950
|
||||||
|
dark:hidden
|
||||||
|
dark:hover:!bg-transparent
|
||||||
|
dark:hover:bg-slate-800
|
||||||
|
dark:hover:bg-slate-800/70
|
||||||
|
dark:hover:text-gray-300
|
||||||
|
dark:hover:text-white
|
||||||
|
dark:opacity-35
|
||||||
|
dark:shadow-gray-950/40
|
||||||
|
dark:text-amber-200
|
||||||
|
dark:text-blue-400
|
||||||
|
dark:text-gray-100
|
||||||
|
dark:text-gray-200
|
||||||
|
dark:text-gray-300
|
||||||
|
dark:text-gray-400
|
||||||
|
dark:text-green-400
|
||||||
|
dark:text-red-200
|
||||||
|
dark:text-red-400
|
||||||
|
dark:text-slate-100
|
||||||
|
dark:text-slate-200
|
||||||
|
dark:text-slate-300
|
||||||
|
dark:text-slate-400
|
||||||
|
dark:text-white
|
||||||
|
dark:to-gray-950
|
||||||
|
dark:via-gray-950
|
||||||
|
disabled:cursor-not-allowed
|
||||||
|
disabled:opacity-40
|
||||||
|
duration-500
|
||||||
|
fixed
|
||||||
|
flex
|
||||||
|
flex-col
|
||||||
|
flex-shrink-0
|
||||||
|
flex-wrap
|
||||||
|
font-bold
|
||||||
|
font-medium
|
||||||
|
font-semibold
|
||||||
|
from-blue-500
|
||||||
|
from-blue-900
|
||||||
|
gap-1
|
||||||
|
gap-12
|
||||||
|
gap-2
|
||||||
|
gap-3
|
||||||
|
gap-6
|
||||||
|
gap-8
|
||||||
|
grid
|
||||||
|
grid-cols-1
|
||||||
|
grid-cols-[auto_1fr_auto]
|
||||||
|
h-10
|
||||||
|
h-5
|
||||||
|
h-9
|
||||||
|
h-[600px]
|
||||||
|
h-full
|
||||||
|
hidden
|
||||||
|
hover:!bg-transparent
|
||||||
|
hover:bg-blue-50
|
||||||
|
hover:bg-red-700
|
||||||
|
hover:bg-sky-700
|
||||||
|
hover:bg-slate-50
|
||||||
|
hover:bg-white/10
|
||||||
|
hover:bg-white/20
|
||||||
|
hover:border-sky-400
|
||||||
|
hover:from-blue-600
|
||||||
|
hover:shadow-xl
|
||||||
|
hover:text-blue-600
|
||||||
|
hover:text-sky-700
|
||||||
|
hover:to-purple-600
|
||||||
|
inline-block
|
||||||
|
inline-flex
|
||||||
|
inset-0
|
||||||
|
inset-x-0
|
||||||
|
items-center
|
||||||
|
items-start
|
||||||
|
justify-between
|
||||||
|
justify-center
|
||||||
|
justify-self-end
|
||||||
|
justify-self-start
|
||||||
|
last:border-b-0
|
||||||
|
last:mb-0
|
||||||
|
leading-relaxed
|
||||||
|
lg:col-start-1
|
||||||
|
lg:col-start-2
|
||||||
|
lg:flex
|
||||||
|
lg:grid-cols-2
|
||||||
|
lg:grid-cols-4
|
||||||
|
lg:grid-cols-5
|
||||||
|
lg:grid-cols-[1fr_auto]
|
||||||
|
lg:hidden
|
||||||
|
lg:justify-self-end
|
||||||
|
max-w-2xl
|
||||||
|
max-w-3xl
|
||||||
|
max-w-4xl
|
||||||
|
max-w-64
|
||||||
|
max-w-[190px]
|
||||||
|
mb-1
|
||||||
|
mb-12
|
||||||
|
mb-16
|
||||||
|
mb-2
|
||||||
|
mb-3
|
||||||
|
mb-4
|
||||||
|
mb-6
|
||||||
|
mb-8
|
||||||
|
md:col-span-4
|
||||||
|
md:flex-row
|
||||||
|
md:grid-cols-12
|
||||||
|
md:grid-cols-2
|
||||||
|
md:grid-cols-3
|
||||||
|
md:mt-0
|
||||||
|
md:text-2xl
|
||||||
|
md:text-6xl
|
||||||
|
min-h-screen
|
||||||
|
min-w-0
|
||||||
|
min-w-full
|
||||||
|
ml-1
|
||||||
|
ml-auto
|
||||||
|
mt-1
|
||||||
|
mt-12
|
||||||
|
mt-3
|
||||||
|
mt-4
|
||||||
|
mt-6
|
||||||
|
mx-1
|
||||||
|
mx-auto
|
||||||
|
object-contain
|
||||||
|
opacity-20
|
||||||
|
overflow-auto
|
||||||
|
overflow-hidden
|
||||||
|
p-3
|
||||||
|
p-4
|
||||||
|
p-5
|
||||||
|
p-8
|
||||||
|
pb-1
|
||||||
|
pb-16
|
||||||
|
pb-6
|
||||||
|
pt-12
|
||||||
|
pt-2
|
||||||
|
pt-20
|
||||||
|
pt-3
|
||||||
|
pt-40
|
||||||
|
pt-6
|
||||||
|
px-3
|
||||||
|
px-4
|
||||||
|
px-6
|
||||||
|
px-8
|
||||||
|
py-1.5
|
||||||
|
py-10
|
||||||
|
py-12
|
||||||
|
py-16
|
||||||
|
py-2
|
||||||
|
py-20
|
||||||
|
py-3
|
||||||
|
py-4
|
||||||
|
py-6
|
||||||
|
relative
|
||||||
|
rounded-2xl
|
||||||
|
rounded-lg
|
||||||
|
rounded-md
|
||||||
|
rounded-xl
|
||||||
|
shadow-black/30
|
||||||
|
shadow-black/40
|
||||||
|
shadow-lg
|
||||||
|
shadow-md
|
||||||
|
shadow-sm
|
||||||
|
shadow-xl
|
||||||
|
sm:grid-cols-2
|
||||||
|
space-x-2
|
||||||
|
space-x-3
|
||||||
|
space-x-4
|
||||||
|
space-x-6
|
||||||
|
space-y-2
|
||||||
|
space-y-3
|
||||||
|
space-y-4
|
||||||
|
text-2xl
|
||||||
|
text-3xl
|
||||||
|
text-4xl
|
||||||
|
text-5xl
|
||||||
|
text-[10px]
|
||||||
|
text-amber-800
|
||||||
|
text-blue-300
|
||||||
|
text-blue-500
|
||||||
|
text-blue-600
|
||||||
|
text-center
|
||||||
|
text-gray-300
|
||||||
|
text-gray-500
|
||||||
|
text-gray-600
|
||||||
|
text-gray-700
|
||||||
|
text-gray-800
|
||||||
|
text-gray-900
|
||||||
|
text-green-500
|
||||||
|
text-green-600
|
||||||
|
text-left
|
||||||
|
text-lg
|
||||||
|
text-purple-600
|
||||||
|
text-red-600
|
||||||
|
text-red-700
|
||||||
|
text-slate-400
|
||||||
|
text-slate-500
|
||||||
|
text-slate-600
|
||||||
|
text-slate-700
|
||||||
|
text-slate-800
|
||||||
|
text-sm
|
||||||
|
text-white
|
||||||
|
text-white/90
|
||||||
|
text-xl
|
||||||
|
text-xs
|
||||||
|
text-yellow-600
|
||||||
|
to-purple-500
|
||||||
|
to-purple-900
|
||||||
|
top-full
|
||||||
|
tracking-wider
|
||||||
|
transition
|
||||||
|
transition-all
|
||||||
|
transition-colors
|
||||||
|
transition-shadow
|
||||||
|
truncate
|
||||||
|
uppercase
|
||||||
|
via-indigo-900
|
||||||
|
w-24
|
||||||
|
w-9
|
||||||
|
w-auto
|
||||||
|
w-full
|
||||||
|
w-px
|
||||||
|
whitespace-nowrap
|
||||||
|
xl:block
|
||||||
|
xl:col-start-2
|
||||||
|
xl:col-start-3
|
||||||
|
xl:grid-cols-[auto_1fr_auto]
|
||||||
|
xl:h-auto
|
||||||
|
xl:justify-self-center
|
||||||
|
xl:max-w-none
|
||||||
|
z-10
|
||||||
|
z-50
|
||||||
|
|
@ -1703,6 +1703,52 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
export const HTML_ELEMENTS: ComponentDefinition[] = [
|
export const HTML_ELEMENTS: ComponentDefinition[] = [
|
||||||
|
{
|
||||||
|
name: 'a',
|
||||||
|
icon: 'Link',
|
||||||
|
category: 'basic',
|
||||||
|
description: 'App.ComponentLink.LinkDescription',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: 'children',
|
||||||
|
type: 'string',
|
||||||
|
value: '',
|
||||||
|
category: 'properties',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'href',
|
||||||
|
type: 'string',
|
||||||
|
value: '',
|
||||||
|
category: 'properties',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'target',
|
||||||
|
type: 'select',
|
||||||
|
value: '',
|
||||||
|
options: ['', '_blank', '_self'],
|
||||||
|
category: 'properties',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'className',
|
||||||
|
type: 'string',
|
||||||
|
value: '',
|
||||||
|
category: 'properties',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'style',
|
||||||
|
type: 'object',
|
||||||
|
value: {},
|
||||||
|
category: 'properties',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'onClick',
|
||||||
|
type: 'string',
|
||||||
|
value: '',
|
||||||
|
category: 'events',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hooks: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'div',
|
name: 'div',
|
||||||
icon: 'Square',
|
icon: 'Square',
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ interface PlatformIconProps {
|
||||||
/** Icon name as stored in the designer, e.g. `FaUser`. */
|
/** Icon name as stored in the designer, e.g. `FaUser`. */
|
||||||
name?: string
|
name?: string
|
||||||
className?: string
|
className?: string
|
||||||
|
/** Piksel cinsinden boyut; verilmezse ikon `className` ile boyutlandirilir. */
|
||||||
|
size?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -12,13 +14,13 @@ interface PlatformIconProps {
|
||||||
* and the generated component need one place that turns a name into an element.
|
* and the generated component need one place that turns a name into an element.
|
||||||
* An unknown or empty name renders nothing, so an unset icon leaves no gap.
|
* An unknown or empty name renders nothing, so an unset icon leaves no gap.
|
||||||
*/
|
*/
|
||||||
const PlatformIcon = ({ name, className }: PlatformIconProps) => {
|
const PlatformIcon = ({ name, className, size }: PlatformIconProps) => {
|
||||||
const navigationIcon = useNavigationIcons()
|
const navigationIcon = useNavigationIcons()
|
||||||
if (!name) return null
|
if (!name) return null
|
||||||
const Icon = navigationIcon[name]
|
const Icon = navigationIcon[name]
|
||||||
// The dictionary loads asynchronously; until it arrives there is nothing to
|
// The dictionary loads asynchronously; until it arrives there is nothing to
|
||||||
// draw, and the component re-renders on its own once it does.
|
// draw, and the component re-renders on its own once it does.
|
||||||
return Icon ? <Icon className={className} /> : null
|
return Icon ? <Icon className={className} size={size} /> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PlatformIcon
|
export default PlatformIcon
|
||||||
|
|
|
||||||
27
ui/src/components/componentEditor/runtimeNodes.ts
Normal file
27
ui/src/components/componentEditor/runtimeNodes.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type React from 'react'
|
||||||
|
import PlatformIcon from './PlatformIcon'
|
||||||
|
import {
|
||||||
|
PublicDemoButton,
|
||||||
|
PublicHeaderBar,
|
||||||
|
PublicLanguageSelector,
|
||||||
|
PublicLogo,
|
||||||
|
PublicMobileMenu,
|
||||||
|
PublicNav,
|
||||||
|
PublicThemeToggle,
|
||||||
|
} from '@/components/publicSite'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Katalogda yer alan ama derlenmis custom component listesinde bulunmayan
|
||||||
|
* dugumler. Kanvas bunlari dogrudan cizer, uretilen kod da ayni adla cagirir;
|
||||||
|
* iki taraf da bu sozlukten beslendigi icin tasarim ve calisma zamani ayrisamaz.
|
||||||
|
*/
|
||||||
|
export const RUNTIME_NODE_COMPONENTS = {
|
||||||
|
PlatformIcon,
|
||||||
|
PublicHeaderBar,
|
||||||
|
PublicLogo,
|
||||||
|
PublicLanguageSelector,
|
||||||
|
PublicThemeToggle,
|
||||||
|
PublicDemoButton,
|
||||||
|
PublicNav,
|
||||||
|
PublicMobileMenu,
|
||||||
|
} as Record<string, React.ComponentType<never>>
|
||||||
|
|
@ -1,626 +1,39 @@
|
||||||
import View from '@/views/Views'
|
import View from '@/views/Views'
|
||||||
import React, { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useLocation, Link, useNavigate } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { FaFacebook, FaTwitter, FaLinkedin, FaInstagram } from 'react-icons/fa'
|
|
||||||
import {
|
|
||||||
LuMenu,
|
|
||||||
LuX,
|
|
||||||
LuHouse,
|
|
||||||
LuInfo,
|
|
||||||
LuBriefcase,
|
|
||||||
LuPackage,
|
|
||||||
LuBookOpen,
|
|
||||||
LuPhone,
|
|
||||||
LuMapPin,
|
|
||||||
LuMail,
|
|
||||||
LuMoon,
|
|
||||||
LuSun,
|
|
||||||
} from 'react-icons/lu'
|
|
||||||
import Logo from '@/views/public/Logo'
|
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
||||||
import LanguageSelector from '../template/LanguageSelector'
|
|
||||||
import Demo from '@/views/public/Demo'
|
import Demo from '@/views/public/Demo'
|
||||||
import { ScrollContext } from '@/contexts/ScrollContext'
|
|
||||||
import { DemoProvider } from '@/contexts/DemoContext'
|
import { DemoProvider } from '@/contexts/DemoContext'
|
||||||
import useDarkMode from '@/utils/hooks/useDarkmode'
|
import { PUBLIC_FOOTER_COMPONENT, PUBLIC_HEADER_COMPONENT } from '@/utils/hooks/usePublicMenu'
|
||||||
import { THEME_ENUM } from '@/constants/theme.constant'
|
import { useComponents } from '@/contexts/ComponentContext'
|
||||||
import { Button } from '../ui'
|
|
||||||
|
|
||||||
interface NavLink {
|
|
||||||
resourceKey?: string
|
|
||||||
name: string
|
|
||||||
path?: string
|
|
||||||
action?: () => void
|
|
||||||
icon?: React.ComponentType<any>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public site kabugu. Ust ve alt bilgi birer Custom Component'tir: tasarimlari
|
||||||
|
* Visual Designer'dan duzenlenir, icerikleri kendi dugum agaclarinda yasar.
|
||||||
|
* Burada yalnizca rota degisiminde basa sarma ve demo penceresi kalir.
|
||||||
|
*/
|
||||||
const PublicLayout = () => {
|
const PublicLayout = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
|
||||||
const { translate } = useLocalization()
|
|
||||||
const [isDarkMode, setThemeMode] = useDarkMode()
|
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
|
||||||
const [scrolled, setScrolled] = useState(false)
|
|
||||||
const [isDemoOpen, setIsDemoOpen] = useState(false)
|
const [isDemoOpen, setIsDemoOpen] = useState(false)
|
||||||
const currentYear = new Date().getFullYear()
|
const { isComponentRegistered, renderComponent } = useComponents()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0)
|
||||||
}, [location.pathname])
|
}, [location.pathname])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleScroll = () => {
|
|
||||||
const isScrolled = window.scrollY > 10
|
|
||||||
if (isScrolled !== scrolled) {
|
|
||||||
setScrolled(isScrolled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('scroll', handleScroll)
|
|
||||||
return () => window.removeEventListener('scroll', handleScroll)
|
|
||||||
}, [scrolled])
|
|
||||||
|
|
||||||
const toggleMenu = () => setIsOpen(!isOpen)
|
|
||||||
|
|
||||||
const navLinks: NavLink[] = [
|
|
||||||
{
|
|
||||||
resourceKey: 'App.Home',
|
|
||||||
name: translate('::App.Home'),
|
|
||||||
path: ROUTES_ENUM.public.home,
|
|
||||||
icon: LuHouse,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resourceKey: 'App.About',
|
|
||||||
name: translate('::App.About'),
|
|
||||||
path: ROUTES_ENUM.public.about,
|
|
||||||
icon: LuInfo,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resourceKey: 'App.Services',
|
|
||||||
name: translate('::App.Services'),
|
|
||||||
path: ROUTES_ENUM.public.services,
|
|
||||||
icon: LuBriefcase,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resourceKey: 'Public.nav.products',
|
|
||||||
name: translate('::App.Platform.Orders'),
|
|
||||||
path: ROUTES_ENUM.public.products,
|
|
||||||
icon: LuPackage,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resourceKey: 'Public.nav.blog',
|
|
||||||
name: translate('::App.Platform.BlogManagement'),
|
|
||||||
path: ROUTES_ENUM.public.blog,
|
|
||||||
icon: LuBookOpen,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resourceKey: 'Public.nav.contact',
|
|
||||||
name: translate('::App.Contact'),
|
|
||||||
path: ROUTES_ENUM.public.contact,
|
|
||||||
icon: LuPhone,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resourceKey: 'App.PublicNav.Giris',
|
|
||||||
name: translate('::App.PublicNav.Giris'),
|
|
||||||
path: ROUTES_ENUM.authenticated.login,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const isLoginLink = (resourceKey?: string) => resourceKey === 'App.PublicNav.Giris'
|
|
||||||
|
|
||||||
const demoButton = (className = '', onClick?: () => void) => (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setIsDemoOpen(true)
|
|
||||||
onClick?.()
|
|
||||||
}}
|
|
||||||
variant="plain"
|
|
||||||
size="sm"
|
|
||||||
className={`group !h-9 !w-9 !bg-transparent !px-0 !text-gray-200 hover:!bg-transparent hover:!text-white !shadow-none !overflow-visible transition-colors ${className}`}
|
|
||||||
>
|
|
||||||
<span className="relative inline-flex h-7 w-7 items-center justify-center overflow-visible rounded-full border-2 border-stone-700 bg-gradient-to-b from-red-400 via-red-600 to-red-800 shadow-[inset_0_-3px_6px_rgba(0,0,0,0.35),0_1px_3px_rgba(0,0,0,0.35)] ring-1 ring-black/60 transition-all duration-200 group-hover:scale-105 group-hover:from-red-300 group-hover:via-red-500 group-hover:to-red-700 group-hover:shadow-[inset_0_-3px_6px_rgba(0,0,0,0.3),0_2px_8px_rgba(239,68,68,0.35)]">
|
|
||||||
<span className="absolute inset-x-1 top-1 h-2 rounded-full bg-white/35 blur-[1px] transition-colors group-hover:bg-white/50" />
|
|
||||||
<span className="absolute left-1/2 top-1/2 z-10 w-12 -translate-x-1/2 -translate-y-1/2 text-center uppercase leading-none tracking-tight text-white drop-shadow">
|
|
||||||
{translate('::App.Demos')}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
|
|
||||||
const themeToggle = (
|
|
||||||
<div className="inline-flex h-9 items-center rounded-lg p-0.5">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setThemeMode(THEME_ENUM.MODE_LIGHT)}
|
|
||||||
aria-pressed={!isDarkMode}
|
|
||||||
title={translate('::App.Theme.LightMode')}
|
|
||||||
icon={<LuSun size={15} strokeWidth={1.8} />}
|
|
||||||
variant="plain"
|
|
||||||
size="xs"
|
|
||||||
className={`!h-8 !w-8 xl:!w-auto !px-0 xl:!px-2.5 !rounded-l-md !rounded-r-none !border-transparent !shadow-none ${
|
|
||||||
!isDarkMode
|
|
||||||
? '!bg-white !text-gray-950'
|
|
||||||
: '!bg-transparent !text-gray-300 hover:!bg-transparent hover:!text-white'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setThemeMode(THEME_ENUM.MODE_DARK)}
|
|
||||||
aria-pressed={isDarkMode}
|
|
||||||
title={translate('::App.Theme.DarkMode')}
|
|
||||||
icon={<LuMoon size={15} strokeWidth={1.8} />}
|
|
||||||
variant="plain"
|
|
||||||
size="xs"
|
|
||||||
className={`!h-8 !w-8 xl:!w-auto !px-0 xl:!px-2.5 !rounded-r-md !rounded-l-none !border-transparent !shadow-none ${
|
|
||||||
isDarkMode
|
|
||||||
? '!bg-gray-300 !text-gray-800'
|
|
||||||
: '!bg-transparent !text-gray-300 hover:!bg-transparent hover:!text-white'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const isActiveLink = (path?: string) => {
|
|
||||||
if (!path) return false
|
|
||||||
if (path === ROUTES_ENUM.public.home) return location.pathname === path
|
|
||||||
return location.pathname.startsWith(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DemoProvider value={{ openDemo: () => setIsDemoOpen(true) }}>
|
<DemoProvider value={{ openDemo: () => setIsDemoOpen(true) }}>
|
||||||
<ScrollContext.Provider value={scrolled}>
|
<div className="flex min-h-screen flex-col overflow-x-hidden">
|
||||||
<div className="flex flex-col min-h-screen">
|
{isComponentRegistered(PUBLIC_HEADER_COMPONENT) &&
|
||||||
{/* HEADER */}
|
renderComponent(PUBLIC_HEADER_COMPONENT)}
|
||||||
<header
|
|
||||||
className={`fixed w-full z-50 transition-all duration-500 ${
|
|
||||||
scrolled
|
|
||||||
? 'bg-gray-950 shadow-lg shadow-black/30 py-2 border-b border-white/10'
|
|
||||||
: 'bg-gray-950/80 backdrop-blur-sm py-4 border-b border-white/5'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="container mx-auto px-6 relative grid grid-cols-[auto_1fr_auto] items-center lg:grid-cols-[1fr_auto] xl:grid-cols-[auto_1fr_auto]">
|
|
||||||
<Logo
|
|
||||||
mode={isDarkMode ? 'dark' : 'light'}
|
|
||||||
className="relative z-10 max-w-[190px] overflow-hidden lg:hidden xl:block xl:max-w-none"
|
|
||||||
imgClass="h-10 w-auto object-contain xl:h-auto"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Desktop / tablet navigation */}
|
<main className="flex-grow">
|
||||||
<nav className="hidden lg:flex lg:col-start-1 xl:col-start-2 items-center justify-self-start xl:justify-self-center gap-1">
|
<View />
|
||||||
{navLinks
|
</main>
|
||||||
.filter((l) => !isLoginLink(l.resourceKey))
|
|
||||||
.map((link) => {
|
|
||||||
const active = isActiveLink(link.path)
|
|
||||||
const baseClass =
|
|
||||||
'relative flex items-center gap-1.5 px-3 py-2 text-sm font-medium rounded-md transition-all duration-200 group'
|
|
||||||
const activeClass = active ? 'text-white' : 'text-gray-300 hover:text-white'
|
|
||||||
return link.path ? (
|
|
||||||
<Link
|
|
||||||
key={link.path}
|
|
||||||
to={link.path}
|
|
||||||
className={`${baseClass} ${activeClass}`}
|
|
||||||
>
|
|
||||||
{link.icon && (
|
|
||||||
<link.icon
|
|
||||||
size={22}
|
|
||||||
strokeWidth={1.75}
|
|
||||||
className={
|
|
||||||
active
|
|
||||||
? 'text-blue-400'
|
|
||||||
: 'text-gray-400 group-hover:text-blue-400 transition-colors'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{link.name}
|
|
||||||
<span
|
|
||||||
className={`absolute bottom-0 left-3 right-3 h-0.5 rounded-full bg-blue-500 transition-all duration-200 ${
|
|
||||||
active ? 'opacity-100' : 'opacity-0 group-hover:opacity-60'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
key={link.name}
|
|
||||||
onClick={link.action}
|
|
||||||
variant="plain"
|
|
||||||
size="sm"
|
|
||||||
className={`${baseClass} ${activeClass}`}
|
|
||||||
>
|
|
||||||
{link.icon && (
|
|
||||||
<link.icon
|
|
||||||
size={22}
|
|
||||||
strokeWidth={1.75}
|
|
||||||
className="text-gray-400 group-hover:text-blue-400 transition-colors"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{link.name}
|
|
||||||
<span className="absolute bottom-0 left-3 right-3 h-0.5 rounded-full bg-blue-500 opacity-0 group-hover:opacity-60 transition-all duration-200" />
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div className="relative z-10 hidden lg:flex lg:col-start-2 xl:col-start-3 lg:justify-self-end items-center gap-3">
|
{isComponentRegistered(PUBLIC_FOOTER_COMPONENT) && renderComponent(PUBLIC_FOOTER_COMPONENT)}
|
||||||
{demoButton('rounded-lg')}
|
|
||||||
<div className="h-5 w-px bg-white/20" />
|
|
||||||
<div className="flex h-9 w-9 items-center justify-center">
|
|
||||||
<LanguageSelector className="!bg-transparent hover:!bg-transparent dark:hover:!bg-transparent" />
|
|
||||||
</div>
|
|
||||||
<div className="h-5 w-px bg-white/20" />
|
|
||||||
{themeToggle}
|
|
||||||
<div className="h-5 w-px bg-white/20" />
|
|
||||||
{navLinks
|
|
||||||
.filter((l) => isLoginLink(l.resourceKey))
|
|
||||||
.map((link) =>
|
|
||||||
link.path ? (
|
|
||||||
<Button
|
|
||||||
key={link.path}
|
|
||||||
type="button"
|
|
||||||
variant="solid"
|
|
||||||
shape="round"
|
|
||||||
onClick={() => navigate(link.path!)}
|
|
||||||
>
|
|
||||||
{link.name}
|
|
||||||
</Button>
|
|
||||||
) : null,
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Mobile Menu Button */}
|
<Demo isOpen={isDemoOpen} onClose={() => setIsDemoOpen(false)} />
|
||||||
<Button
|
</div>
|
||||||
className="lg:hidden col-start-3 justify-self-end flex items-center justify-center w-9 h-9 text-white rounded-md hover:bg-white/10 transition-colors"
|
|
||||||
onClick={toggleMenu}
|
|
||||||
aria-label={translate('::App.Platform.ToggleMenu')}
|
|
||||||
aria-expanded={isOpen}
|
|
||||||
icon={
|
|
||||||
isOpen ? <LuX size={20} strokeWidth={2} /> : <LuMenu size={20} strokeWidth={2} />
|
|
||||||
}
|
|
||||||
variant="plain"
|
|
||||||
size="xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Mobile Navigation */}
|
|
||||||
<div
|
|
||||||
className={`lg:hidden transition-all duration-300 ease-in-out ${
|
|
||||||
isOpen
|
|
||||||
? 'max-h-screen overflow-visible opacity-100'
|
|
||||||
: 'max-h-0 overflow-hidden opacity-0'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="border-t border-white/10 bg-gray-950/98 backdrop-blur-md">
|
|
||||||
<div className="container mx-auto px-6 py-3">
|
|
||||||
<nav className="flex flex-col gap-1">
|
|
||||||
<div className="lg:hidden flex flex-col gap-1">
|
|
||||||
{navLinks.map((link) => {
|
|
||||||
const active = isActiveLink(link.path)
|
|
||||||
const isLogin = isLoginLink(link.resourceKey)
|
|
||||||
if (isLogin) {
|
|
||||||
return link.path ? (
|
|
||||||
<Link
|
|
||||||
key={link.path}
|
|
||||||
to={link.path}
|
|
||||||
onClick={toggleMenu}
|
|
||||||
className="mt-2 flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
{link.name}
|
|
||||||
</Link>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
return link.path ? (
|
|
||||||
<Link
|
|
||||||
key={link.path}
|
|
||||||
to={link.path}
|
|
||||||
onClick={toggleMenu}
|
|
||||||
className={`flex items-center gap-2.5 px-3 py-2.5 text-sm font-medium rounded-md transition-colors ${
|
|
||||||
active
|
|
||||||
? 'bg-white/10 text-white'
|
|
||||||
: 'text-gray-300 hover:bg-white/5 hover:text-white'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{link.icon && (
|
|
||||||
<link.icon
|
|
||||||
size={16}
|
|
||||||
strokeWidth={1.75}
|
|
||||||
className={active ? 'text-blue-400' : 'text-gray-400'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{link.name}
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
key={link.name}
|
|
||||||
onClick={() => {
|
|
||||||
link.action?.()
|
|
||||||
toggleMenu()
|
|
||||||
}}
|
|
||||||
variant="plain"
|
|
||||||
size="sm"
|
|
||||||
className="flex items-center gap-2.5 px-3 py-2.5 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white rounded-md transition-colors text-left"
|
|
||||||
>
|
|
||||||
{link.icon && (
|
|
||||||
<link.icon size={16} strokeWidth={1.75} className="text-gray-400" />
|
|
||||||
)}
|
|
||||||
{link.name}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 border-t border-white/10 pt-2 pb-1">
|
|
||||||
<div className="flex items-center justify-between gap-3">
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{demoButton('rounded-lg', toggleMenu)}
|
|
||||||
<div className="flex h-10 min-w-10 items-center justify-center rounded-lg">
|
|
||||||
<LanguageSelector className="!bg-transparent hover:!bg-transparent dark:hover:!bg-transparent" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex h-10 items-center rounded-lg px-1">{themeToggle}</div>
|
|
||||||
{navLinks
|
|
||||||
.filter((l) => isLoginLink(l.resourceKey))
|
|
||||||
.map((link) =>
|
|
||||||
link.path ? (
|
|
||||||
<Link
|
|
||||||
key={link.path}
|
|
||||||
to={link.path}
|
|
||||||
onClick={toggleMenu}
|
|
||||||
className="hidden h-10 lg:inline-flex items-center justify-center px-4 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-lg shadow-md shadow-blue-900/30 transition-colors"
|
|
||||||
>
|
|
||||||
{link.name}
|
|
||||||
</Link>
|
|
||||||
) : null,
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main className="flex-grow">
|
|
||||||
<View />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer className="bg-gray-900 text-white pt-16 pb-8">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
|
||||||
<div>
|
|
||||||
<Logo mode={isDarkMode ? 'dark' : 'light'} />
|
|
||||||
<p className="mt-4 text-gray-400 text-sm">
|
|
||||||
{translate('::App.PublicFooter.CompanyInfo')}
|
|
||||||
</p>
|
|
||||||
<div className="flex space-x-4 mt-6">
|
|
||||||
<a
|
|
||||||
href="https://facebook.com/sozsoft"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
<FaFacebook size={20} />
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://twitter.com/sozsoft"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
<FaTwitter size={20} />
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://linkedin.com/sozsoft"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
<FaLinkedin size={20} />
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://instagram.com/sozsoft"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
<FaInstagram size={20} />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Quick Links */}
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-bold mb-4 text-white">
|
|
||||||
{translate('::App.PublicFooter.QuickLinksTitle')}
|
|
||||||
</h3>
|
|
||||||
<ul className="space-y-2">
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.home}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.Home')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.products}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.Platform.Orders')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.services}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.Services')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.about}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.About')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.blog}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.Platform.BlogManagement')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.contact}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.Contact')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Services */}
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-bold mb-4 text-white">
|
|
||||||
{translate('::App.PublicServices.ServicesTitle')}
|
|
||||||
</h3>
|
|
||||||
<ul className="space-y-2">
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.services}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.ServicesSoftware.SoftwareTitle')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.services}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.BlogCategories.Webdev')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.services}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.ServicesMobile.MobileTitle')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.services}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.ServicesDatabase.DatabaseTitle')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.services}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.ServicesIntegration.IntegrationTitle')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.services}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.ServicesConsulting.ConsultingTitle')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Contact Info */}
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-bold mb-4 text-white">
|
|
||||||
{translate('::App.Contact')}
|
|
||||||
</h3>
|
|
||||||
<ul className="space-y-3">
|
|
||||||
<li className="flex items-start space-x-3">
|
|
||||||
<LuMapPin
|
|
||||||
size={18}
|
|
||||||
strokeWidth={1.75}
|
|
||||||
className="text-gray-400 mt-1 flex-shrink-0"
|
|
||||||
/>
|
|
||||||
<span className="text-gray-400">{translate('::App.PublicFooter.Address')}</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex items-center space-x-3">
|
|
||||||
<LuPhone
|
|
||||||
size={18}
|
|
||||||
strokeWidth={1.75}
|
|
||||||
className="text-gray-400 flex-shrink-0"
|
|
||||||
/>
|
|
||||||
<a
|
|
||||||
href="tel:+905447697638"
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
+90 (544) 769 7 638
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li className="flex items-center space-x-3">
|
|
||||||
<LuMail
|
|
||||||
size={18}
|
|
||||||
strokeWidth={1.75}
|
|
||||||
className="text-gray-400 flex-shrink-0"
|
|
||||||
/>
|
|
||||||
<a
|
|
||||||
href="mailto:destek@sozsoft.com"
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
destek@sozsoft.com
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-t border-gray-800 mt-12 pt-8">
|
|
||||||
<div className="flex flex-col md:flex-row justify-between items-center">
|
|
||||||
<p className="text-gray-400 text-sm">
|
|
||||||
© {currentYear} Sözsoft Platform. {translate('::App.PublicFooter.Copyright')}
|
|
||||||
</p>
|
|
||||||
<div className="mt-4 md:mt-0">
|
|
||||||
<ul className="flex space-x-6 text-sm">
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.about}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.PublicFooter.PrivacyPolicy')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.about}
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{translate('::App.PublicFooter.TermsOfUse')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
{/* Demo Modal */}
|
|
||||||
<Demo isOpen={isDemoOpen} onClose={() => setIsDemoOpen(false)} />
|
|
||||||
</div>
|
|
||||||
</ScrollContext.Provider>
|
|
||||||
</DemoProvider>
|
</DemoProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ 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 { BillingCycle } from '@/proxy/order/models'
|
||||||
import { BasketData } from '@/utils/basketUtils'
|
import { BasketData } from '@/utils/basketUtils'
|
||||||
import { useScroll } from '@/contexts/ScrollContext'
|
import { useScrolled } from '@/utils/hooks/useScrolled'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { Button } from '@/components/ui'
|
import { Button } from '@/components/ui'
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
|
||||||
basketData,
|
basketData,
|
||||||
}) => {
|
}) => {
|
||||||
const hasBasketItems = basketData.items.length > 0
|
const hasBasketItems = basketData.items.length > 0
|
||||||
const isScrolled = useScroll()
|
const isScrolled = useScrolled()
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
336
ui/src/components/publicSite/index.tsx
Normal file
336
ui/src/components/publicSite/index.tsx
Normal file
|
|
@ -0,0 +1,336 @@
|
||||||
|
/**
|
||||||
|
* Public site basliginin tasarlanabilir parcalari. Her biri Visual Designer
|
||||||
|
* katalogunda bir dugumdur: `PublicHeader` custom component'i bunlari kanvasta
|
||||||
|
* birlestirir, uretilen kod ayni bilesenleri cagirir. Menu, tema, dil ve demo
|
||||||
|
* gibi davranislar burada yasar; yerlesim ve siniflar tasarimciya birakilir.
|
||||||
|
*/
|
||||||
|
import React, { createContext, useContext, useMemo, useState } from 'react'
|
||||||
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
|
import { LuMenu, LuMoon, LuSun, LuX } from 'react-icons/lu'
|
||||||
|
import Logo from '@/views/public/Logo'
|
||||||
|
import LanguageSelector from '@/components/template/LanguageSelector'
|
||||||
|
import { PageIcon } from '@/views/public/shared'
|
||||||
|
import { Button } from '@/components/ui'
|
||||||
|
import { THEME_ENUM } from '@/constants/theme.constant'
|
||||||
|
import useDarkMode from '@/utils/hooks/useDarkmode'
|
||||||
|
import { useScrolled } from '@/utils/hooks/useScrolled'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import { useOptionalDemo } from '@/contexts/DemoContext'
|
||||||
|
import {
|
||||||
|
PUBLIC_MENU_CTA_CLASS,
|
||||||
|
usePublicMenu,
|
||||||
|
type PublicMenuNode,
|
||||||
|
} from '@/utils/hooks/usePublicMenu'
|
||||||
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
|
|
||||||
|
/** Mobil panel acikken bir baglantiya basilinca panel kendini kapatir. */
|
||||||
|
const MobileMenuContext = createContext<(() => void) | null>(null)
|
||||||
|
|
||||||
|
const isCallToAction = (item: PublicMenuNode) =>
|
||||||
|
item.cssClass?.split(/\s+/).includes(PUBLIC_MENU_CTA_CLASS) ?? false
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ HeaderBar
|
||||||
|
|
||||||
|
interface PublicHeaderBarProps {
|
||||||
|
className?: string
|
||||||
|
/** Sayfa kaydirildiginda `className` yerine bu uygulanir; bos birakilirsa baslik degismez. */
|
||||||
|
scrolledClassName?: string
|
||||||
|
/** Kaydirmanin "basladi" sayildigi piksel esigi. */
|
||||||
|
scrollThreshold?: number
|
||||||
|
children?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PublicHeaderBar = ({
|
||||||
|
className = '',
|
||||||
|
scrolledClassName = '',
|
||||||
|
scrollThreshold = 10,
|
||||||
|
children,
|
||||||
|
}: PublicHeaderBarProps) => {
|
||||||
|
const scrolled = useScrolled(scrollThreshold)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className={scrolled && scrolledClassName ? scrolledClassName : className}>
|
||||||
|
{children}
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------- Logo
|
||||||
|
|
||||||
|
interface PublicLogoProps {
|
||||||
|
className?: string
|
||||||
|
imgClassName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logonun aydinlik/karanlik varyantini aktif temadan secer. */
|
||||||
|
export const PublicLogo = ({ className = '', imgClassName = '' }: PublicLogoProps) => {
|
||||||
|
const [isDarkMode] = useDarkMode()
|
||||||
|
|
||||||
|
return <Logo className={className} imgClass={imgClassName} mode={isDarkMode ? 'dark' : 'light'} />
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------- Language
|
||||||
|
|
||||||
|
export const PublicLanguageSelector = ({ className = '' }: { className?: string }) => (
|
||||||
|
<LanguageSelector className={className} />
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- ThemeToggle
|
||||||
|
|
||||||
|
interface PublicThemeToggleProps {
|
||||||
|
className?: string
|
||||||
|
buttonClassName?: string
|
||||||
|
activeClassName?: string
|
||||||
|
iconSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PublicThemeToggle = ({
|
||||||
|
className = 'inline-flex h-9 items-center rounded-lg p-0.5',
|
||||||
|
buttonClassName = '!h-8 !w-8 xl:!w-auto !px-0 xl:!px-2.5 !border-transparent !shadow-none !bg-transparent !text-gray-300 hover:!bg-transparent hover:!text-white',
|
||||||
|
activeClassName = '!bg-white !text-gray-950',
|
||||||
|
iconSize = 15,
|
||||||
|
}: PublicThemeToggleProps) => {
|
||||||
|
const [isDarkMode, setThemeMode] = useDarkMode()
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
|
const modes = [
|
||||||
|
{
|
||||||
|
mode: THEME_ENUM.MODE_LIGHT,
|
||||||
|
active: !isDarkMode,
|
||||||
|
title: '::App.Theme.LightMode',
|
||||||
|
icon: <LuSun size={iconSize} strokeWidth={1.8} />,
|
||||||
|
shape: '!rounded-l-md !rounded-r-none',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: THEME_ENUM.MODE_DARK,
|
||||||
|
active: isDarkMode,
|
||||||
|
title: '::App.Theme.DarkMode',
|
||||||
|
icon: <LuMoon size={iconSize} strokeWidth={1.8} />,
|
||||||
|
shape: '!rounded-r-md !rounded-l-none',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
{modes.map((item) => (
|
||||||
|
<Button
|
||||||
|
key={item.mode}
|
||||||
|
aria-pressed={item.active}
|
||||||
|
className={`${buttonClassName} ${item.shape} ${item.active ? activeClassName : ''}`}
|
||||||
|
icon={item.icon}
|
||||||
|
size="xs"
|
||||||
|
title={translate(item.title)}
|
||||||
|
type="button"
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => setThemeMode(item.mode)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------- DemoButton
|
||||||
|
|
||||||
|
export const PublicDemoButton = ({ className = '' }: { className?: string }) => {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
const { openDemo } = useOptionalDemo()
|
||||||
|
const closeMobileMenu = useContext(MobileMenuContext)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={`group !h-9 !w-9 !overflow-visible !bg-transparent !px-0 !text-gray-200 !shadow-none transition-colors hover:!bg-transparent hover:!text-white ${className}`}
|
||||||
|
size="sm"
|
||||||
|
type="button"
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => {
|
||||||
|
openDemo()
|
||||||
|
closeMobileMenu?.()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="relative inline-flex h-7 w-7 items-center justify-center overflow-visible rounded-full border-2 border-stone-700 bg-gradient-to-b from-red-400 via-red-600 to-red-800 shadow-[inset_0_-3px_6px_rgba(0,0,0,0.35),0_1px_3px_rgba(0,0,0,0.35)] ring-1 ring-black/60 transition-all duration-200 group-hover:scale-105 group-hover:from-red-300 group-hover:via-red-500 group-hover:to-red-700 group-hover:shadow-[inset_0_-3px_6px_rgba(0,0,0,0.3),0_2px_8px_rgba(239,68,68,0.35)]">
|
||||||
|
<span className="absolute inset-x-1 top-1 h-2 rounded-full bg-white/35 blur-[1px] transition-colors group-hover:bg-white/50" />
|
||||||
|
<span className="absolute left-1/2 top-1/2 z-10 w-12 -translate-x-1/2 -translate-y-1/2 text-center uppercase leading-none tracking-tight text-white drop-shadow">
|
||||||
|
{translate('::App.Demos')}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------ Nav
|
||||||
|
|
||||||
|
interface PublicNavProps {
|
||||||
|
/** `navigation` gezinme baglantilari · `actions` vurgulu (cta) kayitlar · `all` hepsi. */
|
||||||
|
source?: 'navigation' | 'actions' | 'all'
|
||||||
|
/** `horizontal` masaustu seridi · `vertical` mobil liste. */
|
||||||
|
variant?: 'horizontal' | 'vertical'
|
||||||
|
className?: string
|
||||||
|
itemClassName?: string
|
||||||
|
activeItemClassName?: string
|
||||||
|
iconSize?: number
|
||||||
|
showIcons?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const HORIZONTAL_ITEM =
|
||||||
|
'group relative flex items-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium text-gray-300 transition-all duration-200 hover:text-white'
|
||||||
|
const VERTICAL_ITEM =
|
||||||
|
'flex items-center gap-2.5 rounded-md px-3 py-2.5 text-sm font-medium text-gray-300 transition-colors hover:bg-white/5 hover:text-white'
|
||||||
|
const ACTION_ITEM =
|
||||||
|
'inline-flex items-center justify-center rounded-full bg-blue-600 px-5 py-2 text-sm font-semibold text-white transition-colors hover:bg-blue-500'
|
||||||
|
const ACTION_ITEM_VERTICAL =
|
||||||
|
'mt-2 flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-blue-500'
|
||||||
|
|
||||||
|
export const PublicNav = ({
|
||||||
|
source = 'navigation',
|
||||||
|
variant = 'horizontal',
|
||||||
|
className = '',
|
||||||
|
itemClassName = '',
|
||||||
|
activeItemClassName = '',
|
||||||
|
iconSize,
|
||||||
|
showIcons = true,
|
||||||
|
}: PublicNavProps) => {
|
||||||
|
const { headerLinks } = usePublicMenu()
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
const location = useLocation()
|
||||||
|
const closeMobileMenu = useContext(MobileMenuContext)
|
||||||
|
|
||||||
|
const isAction = source === 'actions'
|
||||||
|
const horizontal = variant === 'horizontal'
|
||||||
|
const items = useMemo(
|
||||||
|
() =>
|
||||||
|
source === 'all'
|
||||||
|
? headerLinks
|
||||||
|
: headerLinks.filter((item) => isCallToAction(item) === isAction),
|
||||||
|
[headerLinks, isAction, source],
|
||||||
|
)
|
||||||
|
|
||||||
|
const label = (item: PublicMenuNode) => translate(`::${item.displayName ?? item.code}`)
|
||||||
|
const isActive = (url?: string) => {
|
||||||
|
if (!url) return false
|
||||||
|
return url === ROUTES_ENUM.public.home
|
||||||
|
? location.pathname === url
|
||||||
|
: location.pathname.startsWith(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultItemClass = isAction
|
||||||
|
? horizontal
|
||||||
|
? ACTION_ITEM
|
||||||
|
: ACTION_ITEM_VERTICAL
|
||||||
|
: horizontal
|
||||||
|
? HORIZONTAL_ITEM
|
||||||
|
: VERTICAL_ITEM
|
||||||
|
const baseItem = itemClassName || defaultItemClass
|
||||||
|
const activeItem =
|
||||||
|
activeItemClassName || (isAction ? '' : horizontal ? 'text-white' : 'bg-white/10 text-white')
|
||||||
|
// Katalog varsayilani 0'dir: "otomatik" demek, varyanta gore boyut secilir.
|
||||||
|
const size = iconSize || (horizontal ? 22 : 16)
|
||||||
|
|
||||||
|
const itemContent = (item: PublicMenuNode, active: boolean) => (
|
||||||
|
<>
|
||||||
|
{showIcons && (
|
||||||
|
<PageIcon
|
||||||
|
className={
|
||||||
|
active ? 'text-blue-400' : 'text-gray-400 transition-colors group-hover:text-blue-400'
|
||||||
|
}
|
||||||
|
name={item.icon}
|
||||||
|
size={size}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{label(item)}
|
||||||
|
{horizontal && !isAction && (
|
||||||
|
<span
|
||||||
|
className={`absolute bottom-0 left-3 right-3 h-0.5 rounded-full bg-blue-500 transition-all duration-200 ${
|
||||||
|
active ? 'opacity-100' : 'opacity-0 group-hover:opacity-60'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderItem = (item: PublicMenuNode, extraClass = '') => {
|
||||||
|
const active = isActive(item.url)
|
||||||
|
const itemClass = `${baseItem} ${active ? activeItem : ''} ${extraClass}`
|
||||||
|
|
||||||
|
if (!item.url) {
|
||||||
|
return (
|
||||||
|
<span key={item.code} className={`${itemClass} cursor-default`}>
|
||||||
|
{itemContent(item, active)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link key={item.code} className={itemClass} to={item.url} onClick={() => closeMobileMenu?.()}>
|
||||||
|
{itemContent(item, active)}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className={className}>
|
||||||
|
{items.map((item) => {
|
||||||
|
if (!item.children.length) return renderItem(item)
|
||||||
|
|
||||||
|
// Alt menusu olan kayit: masaustunde acilir liste, mobilde girintili satirlar.
|
||||||
|
if (horizontal) {
|
||||||
|
return (
|
||||||
|
<div key={item.code} className="group/menu relative">
|
||||||
|
{renderItem(item)}
|
||||||
|
<div className="invisible absolute left-0 top-full z-20 min-w-[13rem] rounded-lg border border-white/10 bg-gray-950/98 py-2 opacity-0 shadow-xl backdrop-blur-md transition-all duration-150 group-hover/menu:visible group-hover/menu:opacity-100">
|
||||||
|
{item.children.map((child) => renderItem(child, 'px-4 py-2'))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={item.code} className="flex flex-col">
|
||||||
|
{renderItem(item)}
|
||||||
|
{item.children.map((child) => renderItem(child, 'ml-4'))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------- MobileMenu
|
||||||
|
|
||||||
|
interface PublicMobileMenuProps {
|
||||||
|
className?: string
|
||||||
|
buttonClassName?: string
|
||||||
|
panelClassName?: string
|
||||||
|
children?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hamburger dugmesi ve acilir panel. Panelin icerigi tasarimcidan gelir; panel
|
||||||
|
* en yakin konumlandirilmis ust ogenin altina acilir.
|
||||||
|
*/
|
||||||
|
export const PublicMobileMenu = ({
|
||||||
|
className = '',
|
||||||
|
buttonClassName = 'flex h-9 w-9 items-center justify-center rounded-md text-white transition-colors hover:bg-white/10',
|
||||||
|
panelClassName = 'absolute inset-x-0 top-full z-50 border-t border-white/10 bg-gray-950 px-6 py-3 shadow-xl shadow-black/40',
|
||||||
|
children,
|
||||||
|
}: PublicMobileMenuProps) => {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MobileMenuContext.Provider value={() => setOpen(false)}>
|
||||||
|
<div className={className}>
|
||||||
|
<Button
|
||||||
|
aria-expanded={open}
|
||||||
|
aria-label={translate('::App.Platform.ToggleMenu')}
|
||||||
|
className={buttonClassName}
|
||||||
|
icon={open ? <LuX size={20} strokeWidth={2} /> : <LuMenu size={20} strokeWidth={2} />}
|
||||||
|
size="xs"
|
||||||
|
type="button"
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => setOpen((previous) => !previous)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{open && <div className={panelClassName}>{children}</div>}
|
||||||
|
</MobileMenuContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
83
ui/src/components/visualDesigner/CanvasFrame.tsx
Normal file
83
ui/src/components/visualDesigner/CanvasFrame.tsx
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tasarim yuzeyini kendi belgesinde cizer. Tailwind'in `md:`/`lg:` kurallari
|
||||||
|
* tarayici penceresine gore cozulur; kanvas ayni sayfada durdugu surece cihaz
|
||||||
|
* secimi yalnizca paneli daraltir, yerlesimi degistirmez. Ayri bir belge bu
|
||||||
|
* kurallari secilen genislige gore cozer: tasarimcida gorulen, canlida gorulendir.
|
||||||
|
*
|
||||||
|
* Stil etiketleri ust belgeden kopyalanir (uretimde `<link>`, gelistirmede HMR ile
|
||||||
|
* degisen `<style>` etiketleri) ve tema sinifi izlenir.
|
||||||
|
*/
|
||||||
|
const CanvasFrame = ({ width, children }: { width: string; children: React.ReactNode }) => {
|
||||||
|
const frameRef = React.useRef<HTMLIFrameElement>(null)
|
||||||
|
const [frameDocument, setFrameDocument] = React.useState<Document | null>(null)
|
||||||
|
const [height, setHeight] = React.useState(600)
|
||||||
|
|
||||||
|
const attach = React.useCallback(() => {
|
||||||
|
const doc = frameRef.current?.contentDocument
|
||||||
|
if (doc?.body) setFrameDocument(doc)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
React.useEffect(attach, [attach])
|
||||||
|
|
||||||
|
// Ust belgedeki stiller cerceveye kopyalanir ve degisimleri izlenir.
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!frameDocument) return undefined
|
||||||
|
|
||||||
|
const sync = () => {
|
||||||
|
frameDocument.head.querySelectorAll('[data-canvas-style]').forEach((node) => node.remove())
|
||||||
|
document.head.querySelectorAll('style, link[rel="stylesheet"]').forEach((node) => {
|
||||||
|
const copy = node.cloneNode(true) as HTMLElement
|
||||||
|
copy.setAttribute('data-canvas-style', '')
|
||||||
|
frameDocument.head.appendChild(copy)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sync()
|
||||||
|
const observer = new MutationObserver(sync)
|
||||||
|
observer.observe(document.head, { childList: true, subtree: true, characterData: true })
|
||||||
|
return () => observer.disconnect()
|
||||||
|
}, [frameDocument])
|
||||||
|
|
||||||
|
// Tema sinifi: kanvas, uygulamanin aydinlik/karanlik modunu izler.
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!frameDocument) return undefined
|
||||||
|
|
||||||
|
const sync = () => {
|
||||||
|
frameDocument.documentElement.className = document.documentElement.className
|
||||||
|
frameDocument.body.style.margin = '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
sync()
|
||||||
|
const observer = new MutationObserver(sync)
|
||||||
|
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
|
||||||
|
return () => observer.disconnect()
|
||||||
|
}, [frameDocument])
|
||||||
|
|
||||||
|
// Cerceve icerigi kadar uzar; kaydirma disaridaki panelde kalir.
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!frameDocument?.body) return undefined
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(() =>
|
||||||
|
setHeight(Math.max(frameDocument.body.scrollHeight, 200)),
|
||||||
|
)
|
||||||
|
observer.observe(frameDocument.body)
|
||||||
|
return () => observer.disconnect()
|
||||||
|
}, [frameDocument])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
ref={frameRef}
|
||||||
|
className="block border-0 bg-transparent"
|
||||||
|
style={{ width, height }}
|
||||||
|
title="canvas"
|
||||||
|
onLoad={attach}
|
||||||
|
>
|
||||||
|
{frameDocument ? createPortal(children, frameDocument.body) : null}
|
||||||
|
</iframe>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CanvasFrame
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import * as UiKit from '@/components/ui'
|
import * as UiKit from '@/components/ui'
|
||||||
import PlatformIcon from '@/components/componentEditor/PlatformIcon'
|
import PlatformIcon from '@/components/componentEditor/PlatformIcon'
|
||||||
|
import { RUNTIME_NODE_COMPONENTS } from '@/components/componentEditor/runtimeNodes'
|
||||||
import PlatformSelectComponent from '@/components/componentEditor/selectComponents'
|
import PlatformSelectComponent from '@/components/componentEditor/selectComponents'
|
||||||
import PlatformViewHost, {
|
import PlatformViewHost, {
|
||||||
type PlatformViewName,
|
type PlatformViewName,
|
||||||
|
|
@ -1289,6 +1290,18 @@ const renderElement = (
|
||||||
refs,
|
refs,
|
||||||
refOverrides,
|
refOverrides,
|
||||||
)
|
)
|
||||||
|
// Platform düğümleri (ikon, public site parçaları) kataloğun bir parçasıdır ama
|
||||||
|
// derlenmiş custom component listesinde değildir; kanvas onları doğrudan çizer,
|
||||||
|
// üretilen kod da aynı bileşenleri çağırır.
|
||||||
|
const RuntimeNode = RUNTIME_NODE_COMPONENTS[node.type]
|
||||||
|
if (RuntimeNode) {
|
||||||
|
const { children: runtimeChildren, ...runtimeProps } = props
|
||||||
|
return React.createElement(
|
||||||
|
RuntimeNode as React.ComponentType<Record<string, unknown>>,
|
||||||
|
runtimeProps,
|
||||||
|
content ?? (runtimeChildren as React.ReactNode),
|
||||||
|
)
|
||||||
|
}
|
||||||
if (node.kind === 'custom') {
|
if (node.kind === 'custom') {
|
||||||
return renderCustomComponent?.(node.type, { ...props, children: content }) || null
|
return renderCustomComponent?.(node.type, { ...props, children: content }) || null
|
||||||
}
|
}
|
||||||
|
|
@ -1429,6 +1442,88 @@ const HTML_PLACEHOLDER_ELEMENTS = new Set([
|
||||||
'h6',
|
'h6',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kanvasta her dugum kendi sarmalayici div'i icinde cizilir ve ust ogenin grid/flex
|
||||||
|
* duzeninde asil oge o sarmalayici olur. Dugumun kendi yerlesim siniflari sarmalayiciya
|
||||||
|
* tasinmazsa (`md:col-span-4`, `flex-1`, `absolute inset-0`, kenar bosluklari) tasarim
|
||||||
|
* zamani gorunumu canlidan farkli cikar; asagidaki ayrim bunu engeller.
|
||||||
|
*/
|
||||||
|
const HOISTED_LAYOUT_PATTERN =
|
||||||
|
/^(-?m[trblxyse]?-|w-(?!full$|auto$)|basis-|order-|self-|justify-self-|(col|row)-(span|start|end|auto)|\[grid-(column|row):)/
|
||||||
|
const HOISTED_LAYOUT_EXACT = new Set([
|
||||||
|
'flex-1',
|
||||||
|
'flex-auto',
|
||||||
|
'flex-initial',
|
||||||
|
'flex-none',
|
||||||
|
'grow',
|
||||||
|
'grow-0',
|
||||||
|
'shrink',
|
||||||
|
'shrink-0',
|
||||||
|
])
|
||||||
|
/** Sarmalayici da tasir ama dugumden silinmez: ikisi de ayni kutuyu kaplar. */
|
||||||
|
const SHARED_LAYOUT_PATTERN = /^(absolute|fixed|sticky|w-full|h-full)$|^-?(inset|top|right|bottom|left)-|^z-|^(min|max)-w-/
|
||||||
|
const NODE_POSITION_PATTERN = /^(absolute|fixed|sticky)$/
|
||||||
|
|
||||||
|
/** `md:`, `dark:hover:` gibi varyant onlerini atar; `[grid-column:1/-1]` bozulmadan kalir. */
|
||||||
|
const stripVariants = (token: string) => token.replace(/^(?:(?:[\w-]+|\[[^\]]+\]):)+/, '')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kanvas kendi paneline cizer; `fixed` bir dugum tarayici penceresine yapisip
|
||||||
|
* tasarim yuzeyinden tasar. Tasarim zamaninda akista kalmasi icin akisa donusturulur.
|
||||||
|
*/
|
||||||
|
const canvasPositionClass = (token: string) =>
|
||||||
|
stripVariants(token) === 'fixed' ? token.replace(/fixed$/, 'relative') : token
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gorunurluk sinifi dugumde kalirsa sarmalayici yine de bir grid/flex hucresi isgal eder:
|
||||||
|
* mobilde gizlenmesi gereken masaustu seridi yer kaplar ve komsulari yanlis hucreye iter.
|
||||||
|
* Sarmalayiciya `hidden` ile birlikte gorunur hallerinin blok karsiligi verilir; dugumun
|
||||||
|
* kendi `display` degeri (flex/grid) el degmeden kalir, ic duzeni bozulmaz.
|
||||||
|
*/
|
||||||
|
const DISPLAY_CLASSES = new Set([
|
||||||
|
'hidden',
|
||||||
|
'block',
|
||||||
|
'inline-block',
|
||||||
|
'inline',
|
||||||
|
'flex',
|
||||||
|
'inline-flex',
|
||||||
|
'grid',
|
||||||
|
'inline-grid',
|
||||||
|
'table',
|
||||||
|
'flow-root',
|
||||||
|
'contents',
|
||||||
|
'list-item',
|
||||||
|
])
|
||||||
|
|
||||||
|
const wrapperDisplayClass = (token: string, base: string) =>
|
||||||
|
`${token.slice(0, token.length - base.length)}${base === 'hidden' ? 'hidden' : 'block'}`
|
||||||
|
|
||||||
|
const splitLayoutClasses = (className: unknown) => {
|
||||||
|
const hoisted: string[] = []
|
||||||
|
const remaining: string[] = []
|
||||||
|
let positioned = false
|
||||||
|
|
||||||
|
for (const token of String(className ?? '')
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(Boolean)) {
|
||||||
|
const base = stripVariants(token)
|
||||||
|
if (SHARED_LAYOUT_PATTERN.test(base)) {
|
||||||
|
hoisted.push(canvasPositionClass(token))
|
||||||
|
remaining.push(canvasPositionClass(token))
|
||||||
|
positioned ||= NODE_POSITION_PATTERN.test(base)
|
||||||
|
} else if (DISPLAY_CLASSES.has(base)) {
|
||||||
|
hoisted.push(wrapperDisplayClass(token, base))
|
||||||
|
remaining.push(token)
|
||||||
|
} else if (HOISTED_LAYOUT_PATTERN.test(base) || HOISTED_LAYOUT_EXACT.has(base)) {
|
||||||
|
hoisted.push(token)
|
||||||
|
} else {
|
||||||
|
remaining.push(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { hoisted: hoisted.join(' '), remaining: remaining.join(' '), positioned }
|
||||||
|
}
|
||||||
|
|
||||||
const NodeView = ({
|
const NodeView = ({
|
||||||
node,
|
node,
|
||||||
index,
|
index,
|
||||||
|
|
@ -1483,6 +1578,11 @@ const NodeView = ({
|
||||||
const refOverrides = refOverride?.props || {}
|
const refOverrides = refOverride?.props || {}
|
||||||
const refHidden = Boolean(refOverride?.hidden)
|
const refHidden = Boolean(refOverride?.hidden)
|
||||||
const selected = interactive && selectedId === node.id
|
const selected = interactive && selectedId === node.id
|
||||||
|
const layout = splitLayoutClasses(node.props.className)
|
||||||
|
// Tasinan siniflar dugumden dusurulur, yoksa genislik/bosluk iki kez uygulanir.
|
||||||
|
const renderedNode = layout.hoisted
|
||||||
|
? { ...node, props: { ...node.props, className: layout.remaining } }
|
||||||
|
: node
|
||||||
// A nested node can always move: at the edge of its container it is lifted out.
|
// A nested node can always move: at the edge of its container it is lifted out.
|
||||||
// Only the first/last node at the root has nowhere left to go.
|
// Only the first/last node at the root has nowhere left to go.
|
||||||
const canMoveUp = !isRoot || index > 0
|
const canMoveUp = !isRoot || index > 0
|
||||||
|
|
@ -1983,7 +2083,9 @@ const NodeView = ({
|
||||||
return (
|
return (
|
||||||
<DesignerDraggedSubtreeContext.Provider value={insideDraggedSubtree}>
|
<DesignerDraggedSubtreeContext.Provider value={insideDraggedSubtree}>
|
||||||
<div
|
<div
|
||||||
className={`group/node relative min-h-[28px] rounded-md ${refHidden ? 'opacity-40' : ''} ${
|
className={`group/node rounded-md ${layout.positioned ? '' : 'relative'} ${
|
||||||
|
interactive && !layout.positioned ? 'min-h-[28px]' : ''
|
||||||
|
} ${layout.hoisted} ${refHidden ? 'opacity-40' : ''} ${
|
||||||
// Sürüklenen düğümün kendisi soluklaşır; nereye gittiği hedeften okunur.
|
// Sürüklenen düğümün kendisi soluklaşır; nereye gittiği hedeften okunur.
|
||||||
isDragSource ? 'opacity-40' : ''
|
isDragSource ? 'opacity-40' : ''
|
||||||
} ${
|
} ${
|
||||||
|
|
@ -2001,7 +2103,11 @@ const NodeView = ({
|
||||||
}`}
|
}`}
|
||||||
data-designer-node={node.id}
|
data-designer-node={node.id}
|
||||||
draggable={interactive}
|
draggable={interactive}
|
||||||
onClickCapture={() => {
|
onClickCapture={(event) => {
|
||||||
|
// Kanvas gercek sayfa degildir: bir baglantiya tiklamak tasarimciyi terk etmemeli.
|
||||||
|
// Yakalama evresinde durdurulur; react-router'in Link'i de `defaultPrevented`
|
||||||
|
// gorunce gezinmez, tiklama yalnizca secim anlamina gelir.
|
||||||
|
if ((event.target as HTMLElement).closest?.('a[href]')) event.preventDefault()
|
||||||
if (interactive) onSelect?.(node.id)
|
if (interactive) onSelect?.(node.id)
|
||||||
}}
|
}}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
|
|
@ -2163,7 +2269,7 @@ const NodeView = ({
|
||||||
resetKey={JSON.stringify([node.props, node.bindings, node.events])}
|
resetKey={JSON.stringify([node.props, node.bindings, node.events])}
|
||||||
>
|
>
|
||||||
{renderElement(
|
{renderElement(
|
||||||
node,
|
renderedNode,
|
||||||
contentChildren,
|
contentChildren,
|
||||||
dataValues,
|
dataValues,
|
||||||
currentItem,
|
currentItem,
|
||||||
|
|
@ -2178,6 +2284,10 @@ const NodeView = ({
|
||||||
</PreviewBoundary>
|
</PreviewBoundary>
|
||||||
{/* Tabs has a drop zone inside every tab, so it needs no outer placeholder. */}
|
{/* Tabs has a drop zone inside every tab, so it needs no outer placeholder. */}
|
||||||
{interactive &&
|
{interactive &&
|
||||||
|
// Bos bir konteynerin "buraya birak" kutusu her zaman gorunurse ayirici cizgi
|
||||||
|
// gibi kucuk ogeler tasarim yuzeyinde sisip duruyor; yalnizca surukleme
|
||||||
|
// sirasinda ya da dugum seciliyken gosterilir.
|
||||||
|
(dragging || selected) &&
|
||||||
!hasVisibleChildren &&
|
!hasVisibleChildren &&
|
||||||
acceptsDroppedChildren &&
|
acceptsDroppedChildren &&
|
||||||
node.type !== 'Card' &&
|
node.type !== 'Card' &&
|
||||||
|
|
@ -2277,9 +2387,11 @@ const VisualCanvas = ({
|
||||||
<CanvasWidthContext.Provider value={canvasWidth}>
|
<CanvasWidthContext.Provider value={canvasWidth}>
|
||||||
<DesignerRefContext.Provider value={refStore}>
|
<DesignerRefContext.Provider value={refStore}>
|
||||||
<div
|
<div
|
||||||
className={`min-h-full p-8 text-slate-900 transition dark:text-slate-100 ${
|
className={`min-h-full text-slate-900 transition dark:text-slate-100 ${
|
||||||
drag && interactive ? 'bg-sky-50/40 dark:bg-sky-950/20' : ''
|
// Cihaz secildiginde kanvas o cihazin ekranidir: sayfa kenardan kenara cizilir,
|
||||||
}`}
|
// canlida olmayan bir tasarim yuzeyi bosluguyla daralmaz.
|
||||||
|
canvasWidth === 'responsive' ? 'p-8' : ''
|
||||||
|
} ${drag && interactive ? 'bg-sky-50/40 dark:bg-sky-950/20' : ''}`}
|
||||||
onClick={() => interactive && onSelect?.('')}
|
onClick={() => interactive && onSelect?.('')}
|
||||||
onDragOver={(event) => interactive && event.preventDefault()}
|
onDragOver={(event) => interactive && event.preventDefault()}
|
||||||
onDrop={(event) => {
|
onDrop={(event) => {
|
||||||
|
|
@ -2308,7 +2420,7 @@ const VisualCanvas = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{nodes.length ? (
|
{nodes.length ? (
|
||||||
<div className="space-y-4">
|
<div className={canvasWidth === 'responsive' ? 'space-y-4' : ''}>
|
||||||
{nodes.map((node, index) => (
|
{nodes.map((node, index) => (
|
||||||
<NodeView
|
<NodeView
|
||||||
key={node.id}
|
key={node.id}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ interface GeneratedComponentMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
const generatedMetadata = generatedComponentProps as Record<string, GeneratedComponentMetadata>
|
const generatedMetadata = generatedComponentProps as Record<string, GeneratedComponentMetadata>
|
||||||
const CONTAINER_NAMES = new Set(['div', 'Card', 'FormContainer', 'Table', 'Tabs', 'Timeline'])
|
const CONTAINER_NAMES = new Set(['a', 'div', 'Card', 'FormContainer', 'Table', 'Tabs', 'Timeline'])
|
||||||
const getToolboxGroup = (name: string): DesignerComponentDefinition['toolboxGroup'] =>
|
const getToolboxGroup = (name: string): DesignerComponentDefinition['toolboxGroup'] =>
|
||||||
name === 'Table' ? 'layout' : DESIGNER_DATA_COMPONENT_NAMES.has(name) ? 'data' : 'ui'
|
name === 'Table' ? 'layout' : DESIGNER_DATA_COMPONENT_NAMES.has(name) ? 'data' : 'ui'
|
||||||
/** Components intentionally kept out of the toolbox. */
|
/** Components intentionally kept out of the toolbox. */
|
||||||
|
|
@ -875,6 +875,122 @@ const platformDefinition = (
|
||||||
hooks: [],
|
hooks: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public site basliginin parcalari. Menu, tema, dil ve demo davranisi bilesenin
|
||||||
|
* icinde yasar; tasarimci yerlesimi ve siniflari belirler. Kanvas ve uretilen kod
|
||||||
|
* ayni bilesenleri kullanir (`components/publicSite`).
|
||||||
|
*/
|
||||||
|
export const PUBLIC_SITE_COMPONENTS: DesignerComponentDefinition[] = [
|
||||||
|
{
|
||||||
|
name: 'PublicHeaderBar',
|
||||||
|
icon: 'PanelTop',
|
||||||
|
category: 'layout',
|
||||||
|
kind: 'custom',
|
||||||
|
toolboxGroup: 'platform',
|
||||||
|
description: 'App.CatalogPublicHeaderBar.PublicHeaderBarDescription',
|
||||||
|
acceptsChildren: true,
|
||||||
|
properties: [
|
||||||
|
{ name: 'className', type: 'string', value: '', category: 'styling' },
|
||||||
|
{ name: 'scrolledClassName', type: 'string', value: '', category: 'styling' },
|
||||||
|
{ name: 'scrollThreshold', type: 'number', value: 10, category: 'properties' },
|
||||||
|
],
|
||||||
|
hooks: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'PublicNav',
|
||||||
|
icon: 'Menu',
|
||||||
|
category: 'basic',
|
||||||
|
kind: 'custom',
|
||||||
|
toolboxGroup: 'platform',
|
||||||
|
description: 'App.CatalogPublicNav.PublicNavDescription',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: 'source',
|
||||||
|
type: 'select',
|
||||||
|
value: 'navigation',
|
||||||
|
options: ['navigation', 'actions', 'all'],
|
||||||
|
category: 'properties',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'variant',
|
||||||
|
type: 'select',
|
||||||
|
value: 'horizontal',
|
||||||
|
options: ['horizontal', 'vertical'],
|
||||||
|
category: 'properties',
|
||||||
|
},
|
||||||
|
{ name: 'showIcons', type: 'boolean', value: true, category: 'properties' },
|
||||||
|
{ name: 'iconSize', type: 'number', value: 0, category: 'styling' },
|
||||||
|
{ name: 'className', type: 'string', value: '', category: 'styling' },
|
||||||
|
{ name: 'itemClassName', type: 'string', value: '', category: 'styling' },
|
||||||
|
{ name: 'activeItemClassName', type: 'string', value: '', category: 'styling' },
|
||||||
|
],
|
||||||
|
hooks: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'PublicMobileMenu',
|
||||||
|
icon: 'PanelLeft',
|
||||||
|
category: 'layout',
|
||||||
|
kind: 'custom',
|
||||||
|
toolboxGroup: 'platform',
|
||||||
|
description: 'App.CatalogPublicMobileMenu.PublicMobileMenuDescription',
|
||||||
|
acceptsChildren: true,
|
||||||
|
properties: [
|
||||||
|
{ name: 'className', type: 'string', value: '', category: 'styling' },
|
||||||
|
{ name: 'buttonClassName', type: 'string', value: '', category: 'styling' },
|
||||||
|
{ name: 'panelClassName', type: 'string', value: '', category: 'styling' },
|
||||||
|
],
|
||||||
|
hooks: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'PublicLogo',
|
||||||
|
icon: 'Image',
|
||||||
|
category: 'basic',
|
||||||
|
kind: 'custom',
|
||||||
|
toolboxGroup: 'platform',
|
||||||
|
description: 'App.CatalogPublicLogo.PublicLogoDescription',
|
||||||
|
properties: [
|
||||||
|
{ name: 'className', type: 'string', value: '', category: 'styling' },
|
||||||
|
{ name: 'imgClassName', type: 'string', value: '', category: 'styling' },
|
||||||
|
],
|
||||||
|
hooks: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'PublicThemeToggle',
|
||||||
|
icon: 'Sun',
|
||||||
|
category: 'basic',
|
||||||
|
kind: 'custom',
|
||||||
|
toolboxGroup: 'platform',
|
||||||
|
description: 'App.CatalogPublicThemeToggle.PublicThemeToggleDescription',
|
||||||
|
properties: [
|
||||||
|
{ name: 'className', type: 'string', value: '', category: 'styling' },
|
||||||
|
{ name: 'buttonClassName', type: 'string', value: '', category: 'styling' },
|
||||||
|
{ name: 'activeClassName', type: 'string', value: '', category: 'styling' },
|
||||||
|
{ name: 'iconSize', type: 'number', value: 15, category: 'styling' },
|
||||||
|
],
|
||||||
|
hooks: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'PublicLanguageSelector',
|
||||||
|
icon: 'Globe',
|
||||||
|
category: 'basic',
|
||||||
|
kind: 'custom',
|
||||||
|
toolboxGroup: 'platform',
|
||||||
|
description: 'App.CatalogPublicLanguageSelector.PublicLanguageSelectorDescription',
|
||||||
|
properties: [{ name: 'className', type: 'string', value: '', category: 'styling' }],
|
||||||
|
hooks: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'PublicDemoButton',
|
||||||
|
icon: 'Play',
|
||||||
|
category: 'basic',
|
||||||
|
kind: 'custom',
|
||||||
|
toolboxGroup: 'platform',
|
||||||
|
description: 'App.CatalogPublicDemoButton.PublicDemoButtonDescription',
|
||||||
|
properties: [{ name: 'className', type: 'string', value: '', category: 'styling' }],
|
||||||
|
hooks: [],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
export const PLATFORM_COMPONENTS: DesignerComponentDefinition[] = [
|
export const PLATFORM_COMPONENTS: DesignerComponentDefinition[] = [
|
||||||
platformDefinition('ListView', 'List', 'App.CatalogListView.ListViewDescription'),
|
platformDefinition('ListView', 'List', 'App.CatalogListView.ListViewDescription'),
|
||||||
platformDefinition('DataGridView', 'Grid', 'App.CatalogDataGridView.DataGridViewDescription'),
|
platformDefinition('DataGridView', 'Grid', 'App.CatalogDataGridView.DataGridViewDescription'),
|
||||||
|
|
@ -1085,6 +1201,22 @@ export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [
|
||||||
],
|
],
|
||||||
hooks: [],
|
hooks: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Ikonlar ada gore cizilir (menu ikonlariyla ayni sozluk); kanvas ve uretilen
|
||||||
|
// kod ayni `PlatformIcon` bilesenini kullanir.
|
||||||
|
name: 'PlatformIcon',
|
||||||
|
icon: 'Image',
|
||||||
|
category: 'basic',
|
||||||
|
kind: 'custom',
|
||||||
|
toolboxGroup: 'ui',
|
||||||
|
description: 'App.CatalogPlatformIcon.PlatformIconDescription',
|
||||||
|
properties: [
|
||||||
|
{ name: 'name', type: 'string', value: 'FaUser', category: 'properties' },
|
||||||
|
{ name: 'size', type: 'number', value: 24, category: 'styling' },
|
||||||
|
{ name: 'className', type: 'string', value: '', category: 'styling' },
|
||||||
|
],
|
||||||
|
hooks: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Spacer',
|
name: 'Spacer',
|
||||||
icon: 'Space',
|
icon: 'Space',
|
||||||
|
|
@ -1146,6 +1278,7 @@ export const getDesignerCatalog = (customNames: string[] = []): DesignerComponen
|
||||||
...DESIGNER_EXTRAS,
|
...DESIGNER_EXTRAS,
|
||||||
FORM_DEFINITION,
|
FORM_DEFINITION,
|
||||||
...PLATFORM_COMPONENTS,
|
...PLATFORM_COMPONENTS,
|
||||||
|
...PUBLIC_SITE_COMPONENTS,
|
||||||
...html,
|
...html,
|
||||||
...ui,
|
...ui,
|
||||||
...generatedOnlyUi,
|
...generatedOnlyUi,
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,6 @@ export const AI_ASSISTANT = 'App.Definitions.AiBot.Asistant'
|
||||||
/** Veritabanı migration + seed tetikleme yetkisi (host tarafı). */
|
/** Veritabanı migration + seed tetikleme yetkisi (host tarafı). */
|
||||||
export const DB_MIGRATE = 'App.Setup.Migrate'
|
export const DB_MIGRATE = 'App.Setup.Migrate'
|
||||||
|
|
||||||
/**
|
|
||||||
* Public site sayfalarının tasarım modu yetkileri.
|
|
||||||
* Sunucuda `PermissionsData.json` içinde tanımlıdır ve seeder ile yönetilir;
|
|
||||||
* asıl kontrol `PublicAppService.Save*PageAsync` üzerindedir.
|
|
||||||
*/
|
|
||||||
export const PUBLIC_PAGE_DESIGN = {
|
|
||||||
HOME: 'App.Home.Design',
|
|
||||||
ABOUT: 'App.About.Design',
|
|
||||||
SERVICES: 'App.Services.Design',
|
|
||||||
CONTACT: 'App.Contact.Design',
|
|
||||||
} as const
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wizard dosya yöneticisi yetkileri. `PermissionsData.json` içinde tanımlıdır ve seeder ile
|
* Wizard dosya yöneticisi yetkileri. `PermissionsData.json` içinde tanımlıdır ve seeder ile
|
||||||
* yönetilir; buradaki kontroller yalnızca butonları gizler, asıl kontrol
|
* yönetilir; buradaki kontroller yalnızca butonları gizler, asıl kontrol
|
||||||
|
|
|
||||||
|
|
@ -222,11 +222,20 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
||||||
try {
|
try {
|
||||||
// Babel and the UI kit are only needed when an active runtime component
|
// Babel and the UI kit are only needed when an active runtime component
|
||||||
// exists. Keep them out of the application startup bundle.
|
// exists. Keep them out of the application startup bundle.
|
||||||
const [Babel, ui, { default: axios }, { default: PlatformViewHost }] = await Promise.all([
|
const [
|
||||||
|
Babel,
|
||||||
|
ui,
|
||||||
|
{ default: axios },
|
||||||
|
{ default: PlatformViewHost },
|
||||||
|
{ RUNTIME_NODE_COMPONENTS },
|
||||||
|
] = await Promise.all([
|
||||||
import('@babel/standalone'),
|
import('@babel/standalone'),
|
||||||
import('../components/ui'),
|
import('../components/ui'),
|
||||||
import('axios'),
|
import('axios'),
|
||||||
import('../components/componentEditor/PlatformViewHost'),
|
import('../components/componentEditor/PlatformViewHost'),
|
||||||
|
// Katalog dugumleri: react-icons ve public site parcalari runtime
|
||||||
|
// kapsaminda degildir, kanvasin cizdigi bilesenler buradan gecirilir.
|
||||||
|
import('../components/componentEditor/runtimeNodes'),
|
||||||
])
|
])
|
||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
|
|
||||||
|
|
@ -235,6 +244,7 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
||||||
...ui,
|
...ui,
|
||||||
UiKit: ui,
|
UiKit: ui,
|
||||||
PlatformViewHost,
|
PlatformViewHost,
|
||||||
|
...RUNTIME_NODE_COMPONENTS,
|
||||||
apiService,
|
apiService,
|
||||||
DOMPurify,
|
DOMPurify,
|
||||||
axios,
|
axios,
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,10 @@ const DemoContext = createContext<DemoContextValue | undefined>(undefined)
|
||||||
|
|
||||||
export const DemoProvider = DemoContext.Provider
|
export const DemoProvider = DemoContext.Provider
|
||||||
|
|
||||||
export const useDemo = () => {
|
/**
|
||||||
const context = useContext(DemoContext)
|
* Demo penceresini acan cagri. Saglayici disinda cizilen bileşen (tasarimci kanvasi)
|
||||||
|
* de ayni dugumu kullanabilsin diye eksik context bir hataya degil, bos cagriya doner.
|
||||||
if (!context) {
|
*/
|
||||||
throw new Error('useDemo must be used within a DemoProvider')
|
|
||||||
}
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useOptionalDemo = () => {
|
export const useOptionalDemo = () => {
|
||||||
const context = useContext(DemoContext)
|
const context = useContext(DemoContext)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
// ScrollContext.tsx
|
|
||||||
import { createContext, useContext } from 'react'
|
|
||||||
|
|
||||||
export const ScrollContext = createContext<boolean>(false)
|
|
||||||
|
|
||||||
export const useScroll = () => useContext(ScrollContext)
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
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?: string
|
|
||||||
counterSuffix?: string
|
|
||||||
counterDuration?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SectionDto {
|
|
||||||
key: string
|
|
||||||
descKey: string
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
export interface ContactDto {
|
|
||||||
id: string
|
|
||||||
address: string
|
|
||||||
phoneNumber: string
|
|
||||||
email: string
|
|
||||||
location: string
|
|
||||||
taxNumber: string
|
|
||||||
bankDto: BankDto
|
|
||||||
workHoursDto: WorkHoursDto
|
|
||||||
mapDto: MapDto
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BankDto {
|
|
||||||
accountHolder: string
|
|
||||||
branch: string
|
|
||||||
accountNumber: string
|
|
||||||
iban: string
|
|
||||||
styleClass?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WorkHoursDto {
|
|
||||||
weekday: string
|
|
||||||
weekend: string
|
|
||||||
whatsapp: string
|
|
||||||
styleClass?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MapDto {
|
|
||||||
title: string
|
|
||||||
src: string
|
|
||||||
width: string
|
|
||||||
height: string
|
|
||||||
style?: string
|
|
||||||
containerStyleClass?: string
|
|
||||||
frameStyleClass?: string
|
|
||||||
allowFullScreen?: boolean
|
|
||||||
loading: string
|
|
||||||
referrerPolicy: string
|
|
||||||
}
|
|
||||||
|
|
@ -25,6 +25,15 @@ const runtimeComponentCache = new Map<string, React.ComponentType<any>>()
|
||||||
// time instead.
|
// time instead.
|
||||||
const runtimeRenderers = new Map<string, (name: string, props?: any) => React.ReactNode>()
|
const runtimeRenderers = new Map<string, (name: string, props?: any) => React.ReactNode>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rota kaydı, karşılığı olan dosya kaldırıldıktan sonra da veritabanında kalabilir
|
||||||
|
* (seeder yalnızca ekler). Böyle bir kayıt router'a alınmaz; yolu bir custom component
|
||||||
|
* devraldıysa o çizilir, devralmadıysa NotFound gösterilir.
|
||||||
|
*/
|
||||||
|
export function hasPhysicalComponent(componentPath: string) {
|
||||||
|
return `../${componentPath.replace(/^@\//, '')}.tsx` in modules
|
||||||
|
}
|
||||||
|
|
||||||
// Fiziksel komponent yükleme (mevcut mantık)
|
// Fiziksel komponent yükleme (mevcut mantık)
|
||||||
function loadPhysicalComponent(componentPath: string) {
|
function loadPhysicalComponent(componentPath: string) {
|
||||||
const cleanedPath = componentPath.replace(/^@\//, '')
|
const cleanedPath = componentPath.replace(/^@\//, '')
|
||||||
|
|
@ -124,22 +133,24 @@ export interface DynamicReactRoute {
|
||||||
|
|
||||||
// API'den gelen route objesini, React Router için uygun hale getirir
|
// API'den gelen route objesini, React Router için uygun hale getirir
|
||||||
export function mapDynamicRoutes(routes: RouteDto[]): DynamicReactRoute[] {
|
export function mapDynamicRoutes(routes: RouteDto[]): DynamicReactRoute[] {
|
||||||
return routes.map((route) => ({
|
return routes
|
||||||
key: route.path,
|
.filter((route) => route.componentType === 'dynamic' || hasPhysicalComponent(route.componentPath))
|
||||||
path: route.path,
|
.map((route) => ({
|
||||||
getComponent: (registeredComponents, renderComponent, isComponentRegistered) =>
|
key: route.path,
|
||||||
loadComponent(
|
path: route.path,
|
||||||
route.componentType,
|
getComponent: (registeredComponents, renderComponent, isComponentRegistered) =>
|
||||||
route.componentPath,
|
loadComponent(
|
||||||
registeredComponents,
|
route.componentType,
|
||||||
renderComponent,
|
route.componentPath,
|
||||||
isComponentRegistered,
|
registeredComponents,
|
||||||
),
|
renderComponent,
|
||||||
routeType: route.routeType,
|
isComponentRegistered,
|
||||||
authority: route.authority,
|
),
|
||||||
componentType: route.componentType,
|
routeType: route.routeType,
|
||||||
componentPath: route.componentPath,
|
authority: route.authority,
|
||||||
}))
|
componentType: route.componentType,
|
||||||
|
componentPath: route.componentPath,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom components are the single source of truth for runtime routes.
|
// Custom components are the single source of truth for runtime routes.
|
||||||
|
|
|
||||||
|
|
@ -62,10 +62,18 @@ export const DynamicRouter: React.FC = () => {
|
||||||
useComponents()
|
useComponents()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
const dynamicRoutes = React.useMemo(
|
const dynamicRoutes = React.useMemo(() => {
|
||||||
() => [...mapDynamicRoutes(routes), ...mapCustomComponentRoutes(components)],
|
const customComponentRoutes = mapCustomComponentRoutes(components)
|
||||||
[routes, components],
|
const claimedPaths = new Set(customComponentRoutes.map((route) => route.path))
|
||||||
)
|
|
||||||
|
// Ayni yolu iki route paylasirsa custom component kazanir: bir ekranin fiziksel
|
||||||
|
// sayfadan runtime bilesene devri (ve bileseni pasife alarak geri donusu) yalnizca
|
||||||
|
// Custom Components ekranindan yonetilir, rota kaydini silmeyi gerektirmez.
|
||||||
|
return [
|
||||||
|
...mapDynamicRoutes(routes).filter((route) => !claimedPaths.has(route.path)),
|
||||||
|
...customComponentRoutes,
|
||||||
|
]
|
||||||
|
}, [routes, components])
|
||||||
|
|
||||||
// /setup path'inde loading bekleme — setup route her zaman erişilebilir olmalı.
|
// /setup path'inde loading bekleme — setup route her zaman erişilebilir olmalı.
|
||||||
// bootstrapFailed durumu Layouts'ta, layout seçilmeden önce ele alınır.
|
// bootstrapFailed durumu Layouts'ta, layout seçilmeden önce ele alınır.
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,10 @@
|
||||||
public: {
|
public: {
|
||||||
home: '/home',
|
home: '/home',
|
||||||
about: '/about',
|
about: '/about',
|
||||||
aboutDesigner: '/about/designer',
|
|
||||||
products: '/products',
|
products: '/products',
|
||||||
checkout: '/checkout',
|
checkout: '/checkout',
|
||||||
payment: '/payment',
|
payment: '/payment',
|
||||||
success: '/success',
|
success: '/success',
|
||||||
services: '/services',
|
|
||||||
servicesDesigner: '/services/designer',
|
|
||||||
blog: '/blog',
|
blog: '/blog',
|
||||||
blogDetail: '/blog/:id',
|
blogDetail: '/blog/:id',
|
||||||
demo: '/demo',
|
demo: '/demo',
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import { AboutDto } from '@/proxy/about/models'
|
|
||||||
import apiService from './api.service'
|
|
||||||
|
|
||||||
export interface SaveLocalizedTextInput {
|
|
||||||
key: string
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveAboutStatInput {
|
|
||||||
icon: string
|
|
||||||
value: string
|
|
||||||
labelKey: string
|
|
||||||
labelValue: string
|
|
||||||
useCounter?: boolean
|
|
||||||
counterEnd?: string
|
|
||||||
counterSuffix?: string
|
|
||||||
counterDuration?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveAboutSectionInput {
|
|
||||||
titleKey: string
|
|
||||||
titleValue: string
|
|
||||||
descriptionKey: string
|
|
||||||
descriptionValue: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveAboutPageInput {
|
|
||||||
cultureName: string
|
|
||||||
heroTitleKey: string
|
|
||||||
heroTitleValue: string
|
|
||||||
heroSubtitleKey: string
|
|
||||||
heroSubtitleValue: string
|
|
||||||
heroImageKey: string
|
|
||||||
heroImageValue: string
|
|
||||||
stats: SaveAboutStatInput[]
|
|
||||||
descriptions: SaveLocalizedTextInput[]
|
|
||||||
sections: SaveAboutSectionInput[]
|
|
||||||
styleTexts: SaveLocalizedTextInput[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAbout() {
|
|
||||||
return apiService.fetchData<AboutDto>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: '/api/app/public/about',
|
|
||||||
},
|
|
||||||
{ apiName: 'Default' },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveAboutPage(input: SaveAboutPageInput) {
|
|
||||||
return apiService.fetchData<void, SaveAboutPageInput>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/app/public/save-about-page',
|
|
||||||
data: input,
|
|
||||||
},
|
|
||||||
{ apiName: 'Default' },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
import apiService from './api.service'
|
|
||||||
import { ContactDto } from '@/proxy/contact/models'
|
|
||||||
|
|
||||||
export interface SaveLocalizedTextInput {
|
|
||||||
key: string
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getContact() {
|
|
||||||
return apiService.fetchData<ContactDto>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: '/api/app/public/contact',
|
|
||||||
},
|
|
||||||
{ apiName: 'Default' },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveContactPageInput extends Record<string, unknown> {
|
|
||||||
cultureName: string
|
|
||||||
heroTitleKey: string
|
|
||||||
heroTitleValue: string
|
|
||||||
heroSubtitleKey: string
|
|
||||||
heroSubtitleValue: string
|
|
||||||
heroImageKey: string
|
|
||||||
heroImageValue: string
|
|
||||||
contactInfoTitleKey: string
|
|
||||||
contactInfoTitleValue: string
|
|
||||||
addressKey: string
|
|
||||||
addressValue: string
|
|
||||||
phoneNumber: string
|
|
||||||
email: string
|
|
||||||
location: string
|
|
||||||
taxNumber: string
|
|
||||||
bankTitleKey: string
|
|
||||||
bankTitleValue: string
|
|
||||||
bankAccountHolder: string
|
|
||||||
bankBranch: string
|
|
||||||
bankAccountNumber: string
|
|
||||||
bankIban: string
|
|
||||||
bankStyleClass: string
|
|
||||||
workHoursTitleKey: string
|
|
||||||
workHoursTitleValue: string
|
|
||||||
workWeekdayKey: string
|
|
||||||
workWeekdayValue: string
|
|
||||||
workWeekendKey: string
|
|
||||||
workWeekendValue: string
|
|
||||||
workWhatsappKey: string
|
|
||||||
workWhatsappValue: string
|
|
||||||
workHoursStyleClass: string
|
|
||||||
mapTitleKey: string
|
|
||||||
mapTitleValue: string
|
|
||||||
mapSrc: string
|
|
||||||
mapWidth: string
|
|
||||||
mapHeight: string
|
|
||||||
mapAllowFullScreen: boolean
|
|
||||||
mapLoading: string
|
|
||||||
mapReferrerPolicy: string
|
|
||||||
mapContainerStyleClass: string
|
|
||||||
mapFrameStyleClass: string
|
|
||||||
styleTexts: SaveLocalizedTextInput[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveContactPage(input: SaveContactPageInput) {
|
|
||||||
return apiService.fetchData<void>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/app/public/save-contact-page',
|
|
||||||
data: input,
|
|
||||||
},
|
|
||||||
{ apiName: 'Default' },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
30
ui/src/services/country.service.ts
Normal file
30
ui/src/services/country.service.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import apiService from './api.service'
|
||||||
|
|
||||||
|
export interface CountryDto {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
groupName: string;
|
||||||
|
currency: string;
|
||||||
|
phoneCode: number;
|
||||||
|
taxLabel: string;
|
||||||
|
zipRequired: boolean;
|
||||||
|
stateRequired: boolean;
|
||||||
|
|
||||||
|
cities: CityDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CityDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCountry() {
|
||||||
|
return apiService.fetchData<CountryDto[]>(
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/app/public/country',
|
||||||
|
},
|
||||||
|
{ apiName: 'Default' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,179 +0,0 @@
|
||||||
import apiService from './api.service'
|
|
||||||
|
|
||||||
export interface HomeSlideServiceDto {
|
|
||||||
icon: string
|
|
||||||
titleKey: string
|
|
||||||
descriptionKey: string
|
|
||||||
styleClass?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HomeSlideDto {
|
|
||||||
titleKey: string
|
|
||||||
subtitleKey: string
|
|
||||||
styleClass?: string
|
|
||||||
services: HomeSlideServiceDto[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HomeFeatureDto {
|
|
||||||
icon: string
|
|
||||||
titleKey: string
|
|
||||||
descriptionKey: string
|
|
||||||
styleClass?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HomeSolutionDto {
|
|
||||||
icon: string
|
|
||||||
colorClass: string
|
|
||||||
titleKey: string
|
|
||||||
descriptionKey: string
|
|
||||||
styleClass?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HomeDto {
|
|
||||||
id: string
|
|
||||||
heroBackgroundImageKey: string
|
|
||||||
heroPrimaryCtaKey: string
|
|
||||||
heroSecondaryCtaKey: string
|
|
||||||
featuresTitleKey: string
|
|
||||||
featuresSubtitleKey: string
|
|
||||||
solutionsTitleKey: string
|
|
||||||
solutionsSubtitleKey: string
|
|
||||||
ctaTitleKey: string
|
|
||||||
ctaSubtitleKey: string
|
|
||||||
ctaButtonLabelKey: string
|
|
||||||
slidesDto: HomeSlideDto[]
|
|
||||||
featuresDto: HomeFeatureDto[]
|
|
||||||
solutionsDto: HomeSolutionDto[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveHomeSlideServiceInput {
|
|
||||||
icon: string
|
|
||||||
titleKey: string
|
|
||||||
titleValue: string
|
|
||||||
descriptionKey: string
|
|
||||||
descriptionValue: string
|
|
||||||
styleClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveHomeSlideInput {
|
|
||||||
titleKey: string
|
|
||||||
titleValue: string
|
|
||||||
subtitleKey: string
|
|
||||||
subtitleValue: string
|
|
||||||
styleClass: string
|
|
||||||
services: SaveHomeSlideServiceInput[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveHomeFeatureInput {
|
|
||||||
icon: string
|
|
||||||
titleKey: string
|
|
||||||
titleValue: string
|
|
||||||
descriptionKey: string
|
|
||||||
descriptionValue: string
|
|
||||||
styleClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveHomeSolutionInput {
|
|
||||||
icon: string
|
|
||||||
colorClass: string
|
|
||||||
titleKey: string
|
|
||||||
titleValue: string
|
|
||||||
descriptionKey: string
|
|
||||||
descriptionValue: string
|
|
||||||
styleClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveHomePageInput {
|
|
||||||
cultureName: string
|
|
||||||
heroBackgroundImageKey: string
|
|
||||||
heroBackgroundImageValue: string
|
|
||||||
heroPrimaryCtaKey: string
|
|
||||||
heroPrimaryCtaValue: string
|
|
||||||
heroPrimaryCtaStyleKey: string
|
|
||||||
heroPrimaryCtaStyleValue: string
|
|
||||||
heroSecondaryCtaKey: string
|
|
||||||
heroSecondaryCtaValue: string
|
|
||||||
heroSecondaryCtaStyleKey: string
|
|
||||||
heroSecondaryCtaStyleValue: string
|
|
||||||
featuresTitleKey: string
|
|
||||||
featuresTitleValue: string
|
|
||||||
featuresTitleStyleKey: string
|
|
||||||
featuresTitleStyleValue: string
|
|
||||||
featuresSubtitleKey: string
|
|
||||||
featuresSubtitleValue: string
|
|
||||||
featuresSubtitleStyleKey: string
|
|
||||||
featuresSubtitleStyleValue: string
|
|
||||||
solutionsTitleKey: string
|
|
||||||
solutionsTitleValue: string
|
|
||||||
solutionsTitleStyleKey: string
|
|
||||||
solutionsTitleStyleValue: string
|
|
||||||
solutionsSubtitleKey: string
|
|
||||||
solutionsSubtitleValue: string
|
|
||||||
solutionsSubtitleStyleKey: string
|
|
||||||
solutionsSubtitleStyleValue: string
|
|
||||||
ctaTitleKey: string
|
|
||||||
ctaTitleValue: string
|
|
||||||
ctaTitleStyleKey: string
|
|
||||||
ctaTitleStyleValue: string
|
|
||||||
ctaSubtitleKey: string
|
|
||||||
ctaSubtitleValue: string
|
|
||||||
ctaSubtitleStyleKey: string
|
|
||||||
ctaSubtitleStyleValue: string
|
|
||||||
ctaButtonLabelKey: string
|
|
||||||
ctaButtonLabelValue: string
|
|
||||||
ctaButtonStyleKey: string
|
|
||||||
ctaButtonStyleValue: string
|
|
||||||
slides: SaveHomeSlideInput[]
|
|
||||||
features: SaveHomeFeatureInput[]
|
|
||||||
solutions: SaveHomeSolutionInput[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CountryDto {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
name: string;
|
|
||||||
groupName: string;
|
|
||||||
currency: string;
|
|
||||||
phoneCode: number;
|
|
||||||
taxLabel: string;
|
|
||||||
zipRequired: boolean;
|
|
||||||
stateRequired: boolean;
|
|
||||||
|
|
||||||
cities: CityDto[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CityDto {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHome() {
|
|
||||||
return apiService.fetchData<HomeDto>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: '/api/app/public/home',
|
|
||||||
},
|
|
||||||
{ apiName: 'Default' },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCountry() {
|
|
||||||
return apiService.fetchData<CountryDto[]>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: '/api/app/public/country',
|
|
||||||
},
|
|
||||||
{ apiName: 'Default' },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveHomePage(input: SaveHomePageInput) {
|
|
||||||
return apiService.fetchData<void, SaveHomePageInput>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/app/public/save-home-page',
|
|
||||||
data: input,
|
|
||||||
},
|
|
||||||
{ apiName: 'Default' },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
import apiService from './api.service'
|
|
||||||
import { ServiceDto } from '@/proxy/services/models'
|
|
||||||
|
|
||||||
export interface SaveLocalizedTextInput {
|
|
||||||
key: string
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveServiceItemInput {
|
|
||||||
icon?: string
|
|
||||||
titleKey: string
|
|
||||||
titleValue: string
|
|
||||||
descriptionKey?: string
|
|
||||||
descriptionValue?: string
|
|
||||||
type: 'service' | 'support'
|
|
||||||
features: SaveLocalizedTextInput[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveServicesPageInput {
|
|
||||||
cultureName: string
|
|
||||||
heroTitleKey: string
|
|
||||||
heroTitleValue: string
|
|
||||||
heroSubtitleKey: string
|
|
||||||
heroSubtitleValue: string
|
|
||||||
heroImageKey: string
|
|
||||||
heroImageValue: string
|
|
||||||
supportTitleKey: string
|
|
||||||
supportTitleValue: string
|
|
||||||
supportButtonLabelKey: string
|
|
||||||
supportButtonLabelValue: string
|
|
||||||
ctaTitleKey: string
|
|
||||||
ctaTitleValue: string
|
|
||||||
ctaDescriptionKey: string
|
|
||||||
ctaDescriptionValue: string
|
|
||||||
ctaButtonLabelKey: string
|
|
||||||
ctaButtonLabelValue: string
|
|
||||||
serviceItems: SaveServiceItemInput[]
|
|
||||||
supportItems: SaveServiceItemInput[]
|
|
||||||
styleTexts: SaveLocalizedTextInput[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getServices() {
|
|
||||||
return apiService.fetchData<ServiceDto[]>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: '/api/app/public/services-list',
|
|
||||||
},
|
|
||||||
{ apiName: 'Default' },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveServicesPage(input: SaveServicesPageInput) {
|
|
||||||
return apiService.fetchData<void, SaveServicesPageInput>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/app/public/save-services-page',
|
|
||||||
data: input,
|
|
||||||
},
|
|
||||||
{ apiName: 'Default' },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -6,6 +6,7 @@ import appConfig from '../proxy/configs/app.config'
|
||||||
import { NavigationTree } from '../proxy/menus/navigation'
|
import { NavigationTree } from '../proxy/menus/navigation'
|
||||||
import { MenuDto } from '../proxy/menus/models'
|
import { MenuDto } from '../proxy/menus/models'
|
||||||
import getChildren from '../utils/navigation'
|
import getChildren from '../utils/navigation'
|
||||||
|
import { PUBLIC_MENU_MODULE_ID } from '../utils/hooks/usePublicMenu'
|
||||||
import {
|
import {
|
||||||
ApplicationConfigurationDto,
|
ApplicationConfigurationDto,
|
||||||
ApplicationLocalizationRequestDto,
|
ApplicationLocalizationRequestDto,
|
||||||
|
|
@ -108,12 +109,17 @@ export const abpConfigModel: AbpConfigModel = {
|
||||||
const texts = getState().texts
|
const texts = getState().texts
|
||||||
const defaultResourceName = getState().config?.localization.defaultResourceName
|
const defaultResourceName = getState().config?.localization.defaultResourceName
|
||||||
|
|
||||||
result.data.items?.forEach((menu: MenuDto) => {
|
// Public site menusu ziyaretci duzenine aittir; yonetim navigasyonunda yer almaz.
|
||||||
|
const items = (result.data.items ?? []).filter(
|
||||||
|
(menu: MenuDto) => menu.moduleId !== PUBLIC_MENU_MODULE_ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
items.forEach((menu: MenuDto) => {
|
||||||
const displayName = '::' + menu.displayName
|
const displayName = '::' + menu.displayName
|
||||||
menu.displayName = getLocalization(texts || {}, defaultResourceName, displayName)
|
menu.displayName = getLocalization(texts || {}, defaultResourceName, displayName)
|
||||||
})
|
})
|
||||||
|
|
||||||
const menu = getChildren(result.data.items ?? [], null)
|
const menu = getChildren(items, null)
|
||||||
actions.setMenu(menu)
|
actions.setMenu(menu)
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
65
ui/src/utils/hooks/usePublicMenu.ts
Normal file
65
ui/src/utils/hooks/usePublicMenu.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { useStoreState } from '@/store'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public site menu kaydi. Menuler `Pub` modulu altinda tutulur ve uygulama
|
||||||
|
* konfigurasyonunun `publicMenus` alaniyla tasinir; menu ucu `[Authorize]`
|
||||||
|
* oldugu icin ziyaretci ayri bir istek atamaz.
|
||||||
|
*/
|
||||||
|
export interface PublicMenuItem {
|
||||||
|
code: string
|
||||||
|
displayName?: string
|
||||||
|
url?: string
|
||||||
|
icon?: string
|
||||||
|
order: number
|
||||||
|
parentCode?: string
|
||||||
|
cssClass?: string
|
||||||
|
target?: string
|
||||||
|
requiredPermissionName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PublicMenuNode extends PublicMenuItem {
|
||||||
|
children: PublicMenuNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ust menude bu sinifi tasiyan kayit duz baglanti yerine vurgulu buton olarak cizilir. */
|
||||||
|
export const PUBLIC_MENU_CTA_CLASS = 'cta'
|
||||||
|
|
||||||
|
/** Public site menulerinin modulu; yonetim navigasyonu bu modulu disarida birakir. */
|
||||||
|
export const PUBLIC_MENU_MODULE_ID = 'Pub'
|
||||||
|
|
||||||
|
/** Ust ve alt bilgiyi cizen Custom Component'lerin adlari. */
|
||||||
|
export const PUBLIC_HEADER_COMPONENT = 'PublicHeader'
|
||||||
|
export const PUBLIC_FOOTER_COMPONENT = 'PublicFooter'
|
||||||
|
|
||||||
|
export const PUBLIC_MENU_HEADER_CODE = 'App.Public.Header'
|
||||||
|
|
||||||
|
function buildChildren(
|
||||||
|
items: PublicMenuItem[],
|
||||||
|
parentCode: string,
|
||||||
|
isVisible: (item: PublicMenuItem) => boolean,
|
||||||
|
): PublicMenuNode[] {
|
||||||
|
return items
|
||||||
|
.filter((item) => item.parentCode === parentCode && isVisible(item))
|
||||||
|
.sort((left, right) => left.order - right.order)
|
||||||
|
.map((item) => ({ ...item, children: buildChildren(items, item.code, isVisible) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ust menu baglantilarini menu agacindan turetir. Alt bilgi kendi Custom Component'inde
|
||||||
|
* tasarlanir, menuden beslenmez.
|
||||||
|
*/
|
||||||
|
export function usePublicMenu() {
|
||||||
|
const publicMenus = useStoreState(
|
||||||
|
(state) => state.abpConfig.config?.extraProperties?.['publicMenus'],
|
||||||
|
) as PublicMenuItem[] | undefined
|
||||||
|
const grantedPolicies = useStoreState((state) => state.abpConfig.config?.auth?.grantedPolicies)
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const items = Array.isArray(publicMenus) ? publicMenus : []
|
||||||
|
const isVisible = (item: PublicMenuItem) =>
|
||||||
|
!item.requiredPermissionName || grantedPolicies?.[item.requiredPermissionName] === true
|
||||||
|
|
||||||
|
return { headerLinks: buildChildren(items, PUBLIC_MENU_HEADER_CODE, isVisible) }
|
||||||
|
}, [publicMenus, grantedPolicies])
|
||||||
|
}
|
||||||
20
ui/src/utils/hooks/useScrolled.ts
Normal file
20
ui/src/utils/hooks/useScrolled.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sayfa esigin otesine kaydirildi mi. Kabuk artik tasarlanabilir bir Custom
|
||||||
|
* Component oldugu icin durum bir context'ten degil, ihtiyaci olan her bilesenin
|
||||||
|
* kendi dinleyicisinden gelir.
|
||||||
|
*/
|
||||||
|
export const useScrolled = (threshold = 10) => {
|
||||||
|
const [scrolled, setScrolled] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => setScrolled(window.scrollY > threshold)
|
||||||
|
|
||||||
|
handleScroll()
|
||||||
|
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll)
|
||||||
|
}, [threshold])
|
||||||
|
|
||||||
|
return scrolled
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ import {
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { ProfileDto, UpdateProfileDto } from '@/proxy/account/models'
|
import { ProfileDto, UpdateProfileDto } from '@/proxy/account/models'
|
||||||
import { getProfile, updateProfile } from '@/services/account.service'
|
import { getProfile, updateProfile } from '@/services/account.service'
|
||||||
import { CountryDto, getCountry } from '@/services/home.service'
|
import { CountryDto, getCountry } from '@/services/country.service'
|
||||||
import { SelectBoxOption } from '@/types/shared'
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
import { useSetting } from '@/utils/hooks/useSetting'
|
import { useSetting } from '@/utils/hooks/useSetting'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import {
|
||||||
putUserLookout,
|
putUserLookout,
|
||||||
putUserPermission,
|
putUserPermission,
|
||||||
} from '@/services/identity.service'
|
} from '@/services/identity.service'
|
||||||
import { CountryDto, getCountry } from '@/services/home.service'
|
import { CountryDto, getCountry } from '@/services/country.service'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { Field, FieldArray, FieldProps, Form, Formik, FormikHelpers } from 'formik'
|
import { Field, FieldArray, FieldProps, Form, Formik, FormikHelpers } from 'formik'
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ import { getListForms } from '@/services/admin/list-form.service'
|
||||||
import { developerKitService } from '@/services/developerKit.service'
|
import { developerKitService } from '@/services/developerKit.service'
|
||||||
import { Button, Notification, Select, toast } from '@/components/ui'
|
import { Button, Notification, Select, toast } from '@/components/ui'
|
||||||
import StyleModal from '@/components/codeLayout/StyleModal'
|
import StyleModal from '@/components/codeLayout/StyleModal'
|
||||||
|
import { RUNTIME_NODE_COMPONENTS } from '@/components/componentEditor/runtimeNodes'
|
||||||
|
import CanvasFrame from '@/components/visualDesigner/CanvasFrame'
|
||||||
import VisualCanvas, { DESIGNER_DRAG_TYPE } from '@/components/visualDesigner/VisualCanvas'
|
import VisualCanvas, { DESIGNER_DRAG_TYPE } from '@/components/visualDesigner/VisualCanvas'
|
||||||
import {
|
import {
|
||||||
beginDesignerDragSoon,
|
beginDesignerDragSoon,
|
||||||
|
|
@ -843,6 +845,20 @@ const parseGeneratedJsxProps = (
|
||||||
return props
|
return props
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tasarimci ile uretilmis kodu tanir. Elle yazilmis bir bilesenin kodu dugum agacina
|
||||||
|
* cevrilemez: donusum yalnizca tanidigi etiketleri toplar, gerisini (duz HTML, metin,
|
||||||
|
* yardimci bilesenler) sessizce duserdi ve kaydedildiginde kodun kendisi kaybolurdu.
|
||||||
|
* Bu yuzden imzasi olmayan kod "kod modu" olarak acilir.
|
||||||
|
*/
|
||||||
|
const isDesignerGeneratedCode = (code: string | undefined) =>
|
||||||
|
Boolean(
|
||||||
|
code &&
|
||||||
|
(code.includes('__SOZSOFT_VISUAL_DESIGNER__') ||
|
||||||
|
/const \[data_[A-Za-z0-9_$]+, setData_/.test(code) ||
|
||||||
|
code.includes('const designerRefs')),
|
||||||
|
)
|
||||||
|
|
||||||
const migrateGeneratedCodeToDesignerDocument = (
|
const migrateGeneratedCodeToDesignerDocument = (
|
||||||
code: string | undefined,
|
code: string | undefined,
|
||||||
definitions: Map<string, DesignerComponentDefinition>,
|
definitions: Map<string, DesignerComponentDefinition>,
|
||||||
|
|
@ -1475,7 +1491,7 @@ const VisualComponentDesigner = () => {
|
||||||
? null
|
? null
|
||||||
: parseDesignerDocumentFromCode(component.code)
|
: parseDesignerDocumentFromCode(component.code)
|
||||||
const migratedCodeDocument =
|
const migratedCodeDocument =
|
||||||
hasStoredDesignerNodes || codeBackupDocument
|
hasStoredDesignerNodes || codeBackupDocument || !isDesignerGeneratedCode(component.code)
|
||||||
? null
|
? null
|
||||||
: migrateGeneratedCodeToDesignerDocument(component.code, catalogByName)
|
: migrateGeneratedCodeToDesignerDocument(component.code, catalogByName)
|
||||||
const visualDocument = hasStoredDesignerNodes
|
const visualDocument = hasStoredDesignerNodes
|
||||||
|
|
@ -1488,7 +1504,8 @@ const VisualComponentDesigner = () => {
|
||||||
setManualCode(component.code || '')
|
setManualCode(component.code || '')
|
||||||
setSavedCode(component.code || '')
|
setSavedCode(component.code || '')
|
||||||
setSavedDesignerFingerprint(JSON.stringify(initialDocument))
|
setSavedDesignerFingerprint(JSON.stringify(initialDocument))
|
||||||
setWorkspaceTab('design')
|
// Elle yazilmis bilesen kod sekmesiyle acilir; tasarim kanvasi onun icin bos olurdu.
|
||||||
|
setWorkspaceTab(initialDocument.sourceMode === 'visual' ? 'design' : 'code')
|
||||||
setLoadedId(id)
|
setLoadedId(id)
|
||||||
undoStack.current = []
|
undoStack.current = []
|
||||||
redoStack.current = []
|
redoStack.current = []
|
||||||
|
|
@ -2804,10 +2821,14 @@ const VisualComponentDesigner = () => {
|
||||||
[commitDocument, selectDesignerNode],
|
[commitDocument, selectDesignerNode],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Bagimlilik listesi yalnizca baska Custom Component'leri sayar; export bu listeyi izleyerek
|
||||||
|
// bilesenin zincirini toplar. Katalog dugumleri (PlatformIcon, public site parcalari) runtime
|
||||||
|
// kapsamindan gelir, veritabaninda karsiligi yoktur — listeye girerse export cozulemeyen bir
|
||||||
|
// referans arar.
|
||||||
const collectDependencies = useCallback(() => {
|
const collectDependencies = useCallback(() => {
|
||||||
const names = new Set<string>()
|
const names = new Set<string>()
|
||||||
walkDesignerNodes(document.nodes, (node) => {
|
walkDesignerNodes(document.nodes, (node) => {
|
||||||
if (node.kind === 'custom') names.add(node.type)
|
if (node.kind === 'custom' && !(node.type in RUNTIME_NODE_COMPONENTS)) names.add(node.type)
|
||||||
})
|
})
|
||||||
return [...names]
|
return [...names]
|
||||||
}, [document.nodes])
|
}, [document.nodes])
|
||||||
|
|
@ -5372,7 +5393,7 @@ const VisualComponentDesigner = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="truncate text-[11px] text-slate-500">
|
<p className="truncate text-[11px] text-slate-500">
|
||||||
Visual Component Designer · {component.routePath}
|
Visual Component Designer{component.routePath ? ` · ${component.routePath}` : ''}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -5406,21 +5427,25 @@ const VisualComponentDesigner = () => {
|
||||||
>
|
>
|
||||||
{translate('::App.Platform.Reset')}
|
{translate('::App.Platform.Reset')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{/* Rotasi olmayan bilesen (baska bir ekranin icinde cizilen alt bilgi gibi)
|
||||||
color="green-600"
|
tek basina acilamaz; onizleme dugmesi yalnizca rotasi olanda cikar. */}
|
||||||
icon={<FaEye />}
|
{component.routePath?.trim() ? (
|
||||||
size="sm"
|
<Button
|
||||||
title={translate('::App.DeveloperKitComponentDesigner.OpenInNewTab')}
|
color="green-600"
|
||||||
variant="solid"
|
icon={<FaEye />}
|
||||||
onClick={() => {
|
size="sm"
|
||||||
const routePath = component.routePath.startsWith('/')
|
title={translate('::App.DeveloperKitComponentDesigner.OpenInNewTab')}
|
||||||
? component.routePath
|
variant="solid"
|
||||||
: `/${component.routePath}`
|
onClick={() => {
|
||||||
window.open(routePath, '_blank')
|
const routePath = component.routePath.startsWith('/')
|
||||||
}}
|
? component.routePath
|
||||||
>
|
: `/${component.routePath}`
|
||||||
{translate('::App.Listform.ListformField.Preview')}
|
window.open(routePath, '_blank')
|
||||||
</Button>
|
}}
|
||||||
|
>
|
||||||
|
{translate('::App.Listform.ListformField.Preview')}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
<Button
|
<Button
|
||||||
loading={isSaving}
|
loading={isSaving}
|
||||||
icon={<FaRegSave />}
|
icon={<FaRegSave />}
|
||||||
|
|
@ -5602,7 +5627,13 @@ const VisualComponentDesigner = () => {
|
||||||
className="mx-auto min-h-full overflow-hidden rounded-lg bg-white text-slate-900 shadow-lg transition-[width] duration-200 dark:bg-gray-900 dark:text-gray-100"
|
className="mx-auto min-h-full overflow-hidden rounded-lg bg-white text-slate-900 shadow-lg transition-[width] duration-200 dark:bg-gray-900 dark:text-gray-100"
|
||||||
style={{ width: CANVAS_WIDTHS[document.canvas.width], maxWidth: '100%' }}
|
style={{ width: CANVAS_WIDTHS[document.canvas.width], maxWidth: '100%' }}
|
||||||
>
|
>
|
||||||
{canvas}
|
{/* Cihaz secildiginde kanvas kendi belgesinde cizilir: `md:`/`lg:` kurallari
|
||||||
|
tarayici penceresine degil, secilen genislige gore cozulur. */}
|
||||||
|
{document.canvas.width === 'responsive' ? (
|
||||||
|
canvas
|
||||||
|
) : (
|
||||||
|
<CanvasFrame width={CANVAS_WIDTHS[document.canvas.width]}>{canvas}</CanvasFrame>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : workspaceTab === 'data' ? (
|
) : workspaceTab === 'data' ? (
|
||||||
<div className="w-full space-y-4">
|
<div className="w-full space-y-4">
|
||||||
|
|
|
||||||
|
|
@ -1,691 +0,0 @@
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import PageTitle from '@/components/shared/PageTitle'
|
|
||||||
import { AboutDto } from '@/proxy/about/models'
|
|
||||||
import { getAbout, saveAboutPage } from '@/services/about'
|
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
||||||
import { PUBLIC_PAGE_DESIGN } from '@/constants/permission.constant'
|
|
||||||
import {
|
|
||||||
DesignerLayer,
|
|
||||||
DesignerSelection,
|
|
||||||
SelectableBlock,
|
|
||||||
TranslateFn,
|
|
||||||
iconField,
|
|
||||||
imageField,
|
|
||||||
isLikelyLocalizationKey,
|
|
||||||
numberField,
|
|
||||||
booleanField,
|
|
||||||
resolveLocalizedValue,
|
|
||||||
styleField,
|
|
||||||
textAreaField,
|
|
||||||
textField,
|
|
||||||
translateLabel,
|
|
||||||
usePageDesigner,
|
|
||||||
} from './designer'
|
|
||||||
import { designerPageClass, getIconColor } from './pageStyles'
|
|
||||||
import { PageIcon, PublicPageLoader } from './shared'
|
|
||||||
|
|
||||||
interface AboutStatContent {
|
|
||||||
icon: string
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
labelKey: string
|
|
||||||
styleClassKey: string
|
|
||||||
styleClass: string
|
|
||||||
valueStyleClassKey: string
|
|
||||||
valueStyleClass: string
|
|
||||||
labelStyleClassKey: string
|
|
||||||
labelStyleClass: string
|
|
||||||
useCounter: boolean
|
|
||||||
counterEnd: string
|
|
||||||
counterSuffix: string
|
|
||||||
counterDuration: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AboutDescriptionContent {
|
|
||||||
key: string
|
|
||||||
text: string
|
|
||||||
styleClassKey: string
|
|
||||||
styleClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AboutSectionContent {
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
titleKey: string
|
|
||||||
descriptionKey: string
|
|
||||||
cardStyleClassKey: string
|
|
||||||
cardStyleClass: string
|
|
||||||
titleStyleClassKey: string
|
|
||||||
titleStyleClass: string
|
|
||||||
descriptionStyleClassKey: string
|
|
||||||
descriptionStyleClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AboutContent {
|
|
||||||
heroTitle: string
|
|
||||||
heroTitleKey: string
|
|
||||||
heroTitleStyleClassKey: string
|
|
||||||
heroTitleStyleClass: string
|
|
||||||
heroSubtitle: string
|
|
||||||
heroSubtitleKey: string
|
|
||||||
heroSubtitleStyleClassKey: string
|
|
||||||
heroSubtitleStyleClass: string
|
|
||||||
heroImage: string
|
|
||||||
heroImageKey: string
|
|
||||||
heroSectionStyleClassKey: string
|
|
||||||
heroSectionStyleClass: string
|
|
||||||
descriptionsContainerStyleClassKey: string
|
|
||||||
descriptionsContainerStyleClass: string
|
|
||||||
stats: AboutStatContent[]
|
|
||||||
descriptions: AboutDescriptionContent[]
|
|
||||||
sections: AboutSectionContent[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const ABOUT_HERO_IMAGE =
|
|
||||||
'https://images.pexels.com/photos/3183183/pexels-photo-3183183.jpeg?auto=compress&cs=tinysrgb&w=1920'
|
|
||||||
const ABOUT_HERO_TITLE_KEY = 'App.About'
|
|
||||||
const ABOUT_HERO_SUBTITLE_KEY = 'App.PublicAbout.Subtitle'
|
|
||||||
const ABOUT_HERO_IMAGE_KEY = 'Public.about.heroImage'
|
|
||||||
const ABOUT_HERO_SECTION_STYLE_KEY = 'Public.about.hero.sectionStyleClass'
|
|
||||||
const ABOUT_HERO_TITLE_STYLE_KEY = 'Public.about.hero.titleStyleClass'
|
|
||||||
const ABOUT_HERO_SUBTITLE_STYLE_KEY = 'Public.about.hero.subtitleStyleClass'
|
|
||||||
const ABOUT_DESCRIPTIONS_CONTAINER_STYLE_KEY = 'Public.about.descriptions.containerStyleClass'
|
|
||||||
|
|
||||||
const HERO_SECTION_STYLE_DEFAULT = 'relative bg-blue-900 text-white py-12'
|
|
||||||
const HERO_TITLE_STYLE_DEFAULT = 'text-5xl font-bold ml-4 mt-3 mb-2 text-white'
|
|
||||||
const HERO_SUBTITLE_STYLE_DEFAULT = 'text-xl max-w-3xl ml-4'
|
|
||||||
const DESCRIPTIONS_CONTAINER_STYLE_DEFAULT =
|
|
||||||
'p-5 mx-auto text-gray-800 dark:text-gray-200 text-lg leading-relaxed shadow-md bg-white dark:bg-gray-900 border-l-4 border-blue-600'
|
|
||||||
const STAT_STYLE_DEFAULT = 'text-center rounded-xl px-4 py-6'
|
|
||||||
const STAT_VALUE_STYLE_DEFAULT = 'text-4xl font-bold text-gray-900 dark:text-gray-100 mb-2'
|
|
||||||
const STAT_LABEL_STYLE_DEFAULT = 'text-gray-600 dark:text-gray-300'
|
|
||||||
const SECTION_CARD_STYLE_DEFAULT =
|
|
||||||
'bg-white dark:bg-gray-900 p-8 rounded-xl shadow-lg dark:shadow-gray-950/40'
|
|
||||||
const SECTION_TITLE_STYLE_DEFAULT = 'text-2xl font-bold text-gray-900 dark:text-gray-100 mb-4'
|
|
||||||
const SECTION_DESCRIPTION_STYLE_DEFAULT = 'text-gray-700 dark:text-gray-300'
|
|
||||||
|
|
||||||
function getCounterParts(value: string, suffix: string) {
|
|
||||||
const numericMatch = (value || '0').replace(',', '.').match(/\d+(?:\.\d+)?/)
|
|
||||||
|
|
||||||
if (!numericMatch) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const endValue = Number(numericMatch[0])
|
|
||||||
|
|
||||||
if (!Number.isFinite(endValue)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchIndex = numericMatch.index ?? 0
|
|
||||||
|
|
||||||
return {
|
|
||||||
endValue,
|
|
||||||
prefix: value.slice(0, matchIndex),
|
|
||||||
suffix: suffix || value.slice(matchIndex + numericMatch[0].length),
|
|
||||||
hasDecimals: numericMatch[0].includes('.'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sayac animasyonu yalnizca deger ekrana girdiginde bir kez calisir;
|
|
||||||
* boylece sayfanin altindaki istatistikler kullanici oraya gelmeden bitmis olmaz.
|
|
||||||
*/
|
|
||||||
const AnimatedStatValue: FC<{ stat: AboutStatContent }> = ({ stat }) => {
|
|
||||||
const counterParts = useMemo(
|
|
||||||
() => getCounterParts(stat.counterEnd || stat.value, stat.counterSuffix),
|
|
||||||
[stat.counterEnd, stat.counterSuffix, stat.value],
|
|
||||||
)
|
|
||||||
const [currentValue, setCurrentValue] = useState(0)
|
|
||||||
const [isVisible, setIsVisible] = useState(false)
|
|
||||||
const containerRef = useRef<HTMLSpanElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const element = containerRef.current
|
|
||||||
|
|
||||||
if (!stat.useCounter || !element || typeof IntersectionObserver === 'undefined') {
|
|
||||||
setIsVisible(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
(entries) => {
|
|
||||||
if (entries.some((entry) => entry.isIntersecting)) {
|
|
||||||
setIsVisible(true)
|
|
||||||
observer.disconnect()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ threshold: 0.3 },
|
|
||||||
)
|
|
||||||
|
|
||||||
observer.observe(element)
|
|
||||||
|
|
||||||
return () => observer.disconnect()
|
|
||||||
}, [stat.useCounter])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!stat.useCounter || !counterParts || !isVisible) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const duration = Math.max(stat.counterDuration || 2000, 0)
|
|
||||||
let animationFrameId = 0
|
|
||||||
let startTime: number | null = null
|
|
||||||
|
|
||||||
const animate = (timestamp: number) => {
|
|
||||||
startTime ??= timestamp
|
|
||||||
|
|
||||||
const progress = duration === 0 ? 1 : Math.min((timestamp - startTime) / duration, 1)
|
|
||||||
setCurrentValue(counterParts.endValue * (1 - Math.pow(1 - progress, 3)))
|
|
||||||
|
|
||||||
if (progress < 1) {
|
|
||||||
animationFrameId = requestAnimationFrame(animate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentValue(0)
|
|
||||||
animationFrameId = requestAnimationFrame(animate)
|
|
||||||
|
|
||||||
return () => cancelAnimationFrame(animationFrameId)
|
|
||||||
}, [counterParts, isVisible, stat.counterDuration, stat.useCounter])
|
|
||||||
|
|
||||||
if (!stat.useCounter || !counterParts) {
|
|
||||||
return <>{stat.value}</>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span ref={containerRef}>
|
|
||||||
{counterParts.prefix}
|
|
||||||
{counterParts.hasDecimals ? currentValue.toFixed(1) : Math.floor(currentValue)}
|
|
||||||
{counterParts.suffix}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildAboutContent(about: AboutDto | undefined, translate: TranslateFn): AboutContent {
|
|
||||||
return {
|
|
||||||
heroTitle: resolveLocalizedValue(translate, ABOUT_HERO_TITLE_KEY, 'About'),
|
|
||||||
heroTitleKey: ABOUT_HERO_TITLE_KEY,
|
|
||||||
heroTitleStyleClassKey: ABOUT_HERO_TITLE_STYLE_KEY,
|
|
||||||
heroTitleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
ABOUT_HERO_TITLE_STYLE_KEY,
|
|
||||||
HERO_TITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
heroSubtitle: resolveLocalizedValue(translate, ABOUT_HERO_SUBTITLE_KEY),
|
|
||||||
heroSubtitleKey: ABOUT_HERO_SUBTITLE_KEY,
|
|
||||||
heroSubtitleStyleClassKey: ABOUT_HERO_SUBTITLE_STYLE_KEY,
|
|
||||||
heroSubtitleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
ABOUT_HERO_SUBTITLE_STYLE_KEY,
|
|
||||||
HERO_SUBTITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
heroImage: resolveLocalizedValue(translate, ABOUT_HERO_IMAGE_KEY, ABOUT_HERO_IMAGE),
|
|
||||||
heroImageKey: ABOUT_HERO_IMAGE_KEY,
|
|
||||||
heroSectionStyleClassKey: ABOUT_HERO_SECTION_STYLE_KEY,
|
|
||||||
heroSectionStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
ABOUT_HERO_SECTION_STYLE_KEY,
|
|
||||||
HERO_SECTION_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
descriptionsContainerStyleClassKey: ABOUT_DESCRIPTIONS_CONTAINER_STYLE_KEY,
|
|
||||||
descriptionsContainerStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
ABOUT_DESCRIPTIONS_CONTAINER_STYLE_KEY,
|
|
||||||
DESCRIPTIONS_CONTAINER_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
stats: (about?.statsDto ?? []).map((stat, index) => {
|
|
||||||
const prefix = `Public.about.dynamic.stat.${index + 1}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
icon: stat.icon || '',
|
|
||||||
value: stat.value,
|
|
||||||
label: resolveLocalizedValue(translate, stat.labelKey, stat.labelKey),
|
|
||||||
labelKey:
|
|
||||||
(isLikelyLocalizationKey(stat.labelKey) ? stat.labelKey : '') || `${prefix}.label`,
|
|
||||||
styleClassKey: `${prefix}.styleClass`,
|
|
||||||
styleClass: resolveLocalizedValue(translate, `${prefix}.styleClass`, STAT_STYLE_DEFAULT),
|
|
||||||
valueStyleClassKey: `${prefix}.valueStyleClass`,
|
|
||||||
valueStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.valueStyleClass`,
|
|
||||||
STAT_VALUE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
labelStyleClassKey: `${prefix}.labelStyleClass`,
|
|
||||||
labelStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.labelStyleClass`,
|
|
||||||
STAT_LABEL_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
useCounter: stat.useCounter ?? false,
|
|
||||||
counterEnd: stat.counterEnd ?? '',
|
|
||||||
counterSuffix: stat.counterSuffix ?? '',
|
|
||||||
counterDuration: stat.counterDuration ?? 2000,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
descriptions: (about?.descriptionsDto ?? []).map((item, index) => {
|
|
||||||
const prefix = `Public.about.dynamic.description.${index + 1}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: (isLikelyLocalizationKey(item) ? item : '') || prefix,
|
|
||||||
text: resolveLocalizedValue(translate, item, item),
|
|
||||||
styleClassKey: `${prefix}.styleClass`,
|
|
||||||
styleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.styleClass`,
|
|
||||||
index % 2 === 0 ? '' : 'text-center p-5 text-blue-800 dark:text-white',
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
sections: (about?.sectionsDto ?? []).map((section) => {
|
|
||||||
const prefix = `Public.about.dynamic.section.${section.key}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: resolveLocalizedValue(translate, section.key, section.key),
|
|
||||||
description: resolveLocalizedValue(translate, section.descKey, section.descKey),
|
|
||||||
titleKey: (isLikelyLocalizationKey(section.key) ? section.key : '') || `${prefix}.title`,
|
|
||||||
descriptionKey:
|
|
||||||
(isLikelyLocalizationKey(section.descKey) ? section.descKey : '') ||
|
|
||||||
`${prefix}.description`,
|
|
||||||
cardStyleClassKey: `${prefix}.cardStyleClass`,
|
|
||||||
cardStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.cardStyleClass`,
|
|
||||||
SECTION_CARD_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
titleStyleClassKey: `${prefix}.titleStyleClass`,
|
|
||||||
titleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.titleStyleClass`,
|
|
||||||
SECTION_TITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
descriptionStyleClassKey: `${prefix}.descriptionStyleClass`,
|
|
||||||
descriptionStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.descriptionStyleClass`,
|
|
||||||
SECTION_DESCRIPTION_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toSaveInput(content: AboutContent, cultureName: string) {
|
|
||||||
return {
|
|
||||||
cultureName,
|
|
||||||
heroTitleKey: content.heroTitleKey,
|
|
||||||
heroTitleValue: content.heroTitle,
|
|
||||||
heroSubtitleKey: content.heroSubtitleKey,
|
|
||||||
heroSubtitleValue: content.heroSubtitle,
|
|
||||||
heroImageKey: content.heroImageKey,
|
|
||||||
heroImageValue: content.heroImage,
|
|
||||||
stats: content.stats.map((stat, index) => ({
|
|
||||||
icon: stat.icon,
|
|
||||||
value: stat.value,
|
|
||||||
labelKey: stat.labelKey || `Public.about.dynamic.stat.${index + 1}.label`,
|
|
||||||
labelValue: stat.label,
|
|
||||||
useCounter: stat.useCounter,
|
|
||||||
counterEnd: stat.counterEnd,
|
|
||||||
counterSuffix: stat.counterSuffix,
|
|
||||||
counterDuration: stat.counterDuration,
|
|
||||||
})),
|
|
||||||
descriptions: content.descriptions.map((item, index) => ({
|
|
||||||
key: item.key || `Public.about.dynamic.description.${index + 1}`,
|
|
||||||
value: item.text,
|
|
||||||
})),
|
|
||||||
sections: content.sections.map((section, index) => ({
|
|
||||||
titleKey: section.titleKey || `Public.about.dynamic.section.${index + 1}.title`,
|
|
||||||
titleValue: section.title,
|
|
||||||
descriptionKey:
|
|
||||||
section.descriptionKey || `Public.about.dynamic.section.${index + 1}.description`,
|
|
||||||
descriptionValue: section.description,
|
|
||||||
})),
|
|
||||||
styleTexts: [
|
|
||||||
{ key: content.heroSectionStyleClassKey, value: content.heroSectionStyleClass },
|
|
||||||
{ key: content.heroTitleStyleClassKey, value: content.heroTitleStyleClass },
|
|
||||||
{ key: content.heroSubtitleStyleClassKey, value: content.heroSubtitleStyleClass },
|
|
||||||
{
|
|
||||||
key: content.descriptionsContainerStyleClassKey,
|
|
||||||
value: content.descriptionsContainerStyleClass,
|
|
||||||
},
|
|
||||||
...content.stats.flatMap((stat) => [
|
|
||||||
{ key: stat.styleClassKey, value: stat.styleClass },
|
|
||||||
{ key: stat.valueStyleClassKey, value: stat.valueStyleClass },
|
|
||||||
{ key: stat.labelStyleClassKey, value: stat.labelStyleClass },
|
|
||||||
]),
|
|
||||||
...content.descriptions.map((item) => ({
|
|
||||||
key: item.styleClassKey,
|
|
||||||
value: item.styleClass,
|
|
||||||
})),
|
|
||||||
...content.sections.flatMap((section) => [
|
|
||||||
{ key: section.cardStyleClassKey, value: section.cardStyleClass },
|
|
||||||
{ key: section.titleStyleClassKey, value: section.titleStyleClass },
|
|
||||||
{ key: section.descriptionStyleClassKey, value: section.descriptionStyleClass },
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const About: FC = () => {
|
|
||||||
const { translate } = useLocalization()
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [about, setAbout] = useState<AboutDto>()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchAbout = async () => {
|
|
||||||
setLoading(true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await getAbout()
|
|
||||||
setAbout(result.data)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('About alinirken hata olustu:', error)
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchAbout()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Referansi kararli tutmak icin memoize edilir; aksi halde tasarim state'i her render'da yeniden tohumlanir.
|
|
||||||
const initialContent = useMemo(
|
|
||||||
() => (loading ? null : buildAboutContent(about, translate)),
|
|
||||||
[about, loading, translate],
|
|
||||||
)
|
|
||||||
|
|
||||||
const designer = usePageDesigner<AboutContent>({
|
|
||||||
pageKey: 'About',
|
|
||||||
permission: PUBLIC_PAGE_DESIGN.ABOUT,
|
|
||||||
initialContent,
|
|
||||||
onSave: (content, cultureName) => saveAboutPage(toSaveInput(content, cultureName)),
|
|
||||||
})
|
|
||||||
|
|
||||||
const { content, isDesignMode, isPanelVisible, selectedBlockId, selectBlock } = designer
|
|
||||||
|
|
||||||
const selection: DesignerSelection | null = useMemo(() => {
|
|
||||||
if (!content || !selectedBlockId) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'hero') {
|
|
||||||
return {
|
|
||||||
id: 'hero',
|
|
||||||
title: content.heroTitleKey,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc4'),
|
|
||||||
fields: [
|
|
||||||
textField('heroTitle', content.heroTitleKey, content.heroTitle),
|
|
||||||
textAreaField('heroSubtitle', content.heroSubtitleKey, content.heroSubtitle),
|
|
||||||
imageField('heroImage', content.heroImageKey, content.heroImage, { group: 'media' }),
|
|
||||||
styleField(
|
|
||||||
'heroTitleStyleClass',
|
|
||||||
content.heroTitleStyleClassKey,
|
|
||||||
content.heroTitleStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'heroSubtitleStyleClass',
|
|
||||||
content.heroSubtitleStyleClassKey,
|
|
||||||
content.heroSubtitleStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'heroSectionStyleClass',
|
|
||||||
content.heroSectionStyleClassKey,
|
|
||||||
content.heroSectionStyleClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'descriptions') {
|
|
||||||
return {
|
|
||||||
id: 'descriptions',
|
|
||||||
title: 'Public.about.description.*',
|
|
||||||
description: translateLabel(translate, 'Public.designer.descriptionsHint'),
|
|
||||||
fields: [
|
|
||||||
...content.descriptions.map((item, index) =>
|
|
||||||
textAreaField(`descriptions.${index}.text`, item.key, item.text, {
|
|
||||||
rows: index % 2 === 0 ? 4 : 3,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'descriptionsContainerStyleClass',
|
|
||||||
content.descriptionsContainerStyleClassKey,
|
|
||||||
content.descriptionsContainerStyleClass,
|
|
||||||
),
|
|
||||||
...content.descriptions.map((item, index) =>
|
|
||||||
styleField(`descriptions.${index}.styleClass`, item.styleClassKey, item.styleClass),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId.startsWith('stat-')) {
|
|
||||||
const index = Number(selectedBlockId.replace('stat-', ''))
|
|
||||||
const stat = content.stats[index]
|
|
||||||
|
|
||||||
if (!stat) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: stat.labelKey,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc1'),
|
|
||||||
fields: [
|
|
||||||
iconField(
|
|
||||||
`stats.${index}.icon`,
|
|
||||||
translateLabel(translate, 'App.PublicDesigner.IkonAnahtari'),
|
|
||||||
stat.icon,
|
|
||||||
{ placeholder: translate('::App.PublicDesigner.IconPlaceholder', { icon: 'FaUsers' }) },
|
|
||||||
),
|
|
||||||
textField(
|
|
||||||
`stats.${index}.value`,
|
|
||||||
translate('::App.Listform.ListformField.Value'),
|
|
||||||
stat.value,
|
|
||||||
),
|
|
||||||
textField(`stats.${index}.label`, stat.labelKey, stat.label),
|
|
||||||
booleanField(`stats.${index}.useCounter`, 'useCounter', stat.useCounter, {
|
|
||||||
group: 'advanced',
|
|
||||||
placeholder: translateLabel(translate, 'Public.designer.useCounter'),
|
|
||||||
}),
|
|
||||||
textField(`stats.${index}.counterEnd`, 'counterEnd', stat.counterEnd, {
|
|
||||||
group: 'advanced',
|
|
||||||
helpText: translateLabel(translate, 'Public.designer.counterEndHint'),
|
|
||||||
}),
|
|
||||||
textField(`stats.${index}.counterSuffix`, 'counterSuffix', stat.counterSuffix, {
|
|
||||||
group: 'advanced',
|
|
||||||
}),
|
|
||||||
numberField(
|
|
||||||
`stats.${index}.counterDuration`,
|
|
||||||
'counterDuration (ms)',
|
|
||||||
stat.counterDuration,
|
|
||||||
{
|
|
||||||
group: 'advanced',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
styleField(`stats.${index}.styleClass`, stat.styleClassKey, stat.styleClass),
|
|
||||||
styleField(
|
|
||||||
`stats.${index}.valueStyleClass`,
|
|
||||||
stat.valueStyleClassKey,
|
|
||||||
stat.valueStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
`stats.${index}.labelStyleClass`,
|
|
||||||
stat.labelStyleClassKey,
|
|
||||||
stat.labelStyleClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId.startsWith('section-')) {
|
|
||||||
const index = Number(selectedBlockId.replace('section-', ''))
|
|
||||||
const section = content.sections[index]
|
|
||||||
|
|
||||||
if (!section) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: section.titleKey,
|
|
||||||
description: translateLabel(translate, 'Public.designer.sectionHint'),
|
|
||||||
fields: [
|
|
||||||
textField(`sections.${index}.title`, section.titleKey, section.title),
|
|
||||||
textAreaField(
|
|
||||||
`sections.${index}.description`,
|
|
||||||
section.descriptionKey,
|
|
||||||
section.description,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
`sections.${index}.cardStyleClass`,
|
|
||||||
section.cardStyleClassKey,
|
|
||||||
section.cardStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
`sections.${index}.titleStyleClass`,
|
|
||||||
section.titleStyleClassKey,
|
|
||||||
section.titleStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
`sections.${index}.descriptionStyleClass`,
|
|
||||||
section.descriptionStyleClassKey,
|
|
||||||
section.descriptionStyleClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}, [content, selectedBlockId, translate])
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <PublicPageLoader />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageTitle title={translate('::App.About')} />
|
|
||||||
|
|
||||||
<div className={designerPageClass(isDesignMode, isPanelVisible)}>
|
|
||||||
{/* Hero */}
|
|
||||||
<SelectableBlock
|
|
||||||
id="hero"
|
|
||||||
label="Hero"
|
|
||||||
isActive={selectedBlockId === 'hero'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`${content?.heroSectionStyleClass || HERO_SECTION_STYLE_DEFAULT} dark:bg-gray-950`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 opacity-20 dark:opacity-35"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url("${content?.heroImage || ABOUT_HERO_IMAGE}")`,
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'center',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-blue-950/25 dark:bg-gray-950/45" />
|
|
||||||
<div className="container relative mx-auto pt-20">
|
|
||||||
<h1 className={content?.heroTitleStyleClass || HERO_TITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.heroTitle}
|
|
||||||
</h1>
|
|
||||||
<p className={content?.heroSubtitleStyleClass || HERO_SUBTITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.heroSubtitle}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
|
|
||||||
{/* Stats */}
|
|
||||||
<div className="bg-white py-10 dark:bg-gray-900">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-5">
|
|
||||||
{content?.stats.map((stat, index) => (
|
|
||||||
<SelectableBlock
|
|
||||||
key={`${stat.labelKey}-${index}`}
|
|
||||||
id={`stat-${index}`}
|
|
||||||
label={`Stat ${index + 1}`}
|
|
||||||
isActive={selectedBlockId === `stat-${index}`}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={stat.styleClass || STAT_STYLE_DEFAULT}>
|
|
||||||
<PageIcon
|
|
||||||
name={stat.icon}
|
|
||||||
className={`mx-auto mb-4 h-12 w-12 ${getIconColor(index)}`}
|
|
||||||
/>
|
|
||||||
<div className={stat.valueStyleClass || STAT_VALUE_STYLE_DEFAULT}>
|
|
||||||
<AnimatedStatValue stat={stat} />
|
|
||||||
</div>
|
|
||||||
<div className={stat.labelStyleClass || STAT_LABEL_STYLE_DEFAULT}>
|
|
||||||
{stat.label}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Descriptions + sections */}
|
|
||||||
<div className="py-6">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<div className="mb-6">
|
|
||||||
<SelectableBlock
|
|
||||||
id="descriptions"
|
|
||||||
label={translateLabel(translate, 'Public.designer.group.content')}
|
|
||||||
isActive={selectedBlockId === 'descriptions'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
content?.descriptionsContainerStyleClass || DESCRIPTIONS_CONTAINER_STYLE_DEFAULT
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{content?.descriptions.map((item, index) => (
|
|
||||||
<p key={item.key || `description-${index}`} className={item.styleClass}>
|
|
||||||
{item.text}
|
|
||||||
</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-12 lg:grid-cols-2">
|
|
||||||
{content?.sections.map((section, index) => (
|
|
||||||
<SelectableBlock
|
|
||||||
key={`${section.titleKey}-${index}`}
|
|
||||||
id={`section-${index}`}
|
|
||||||
label={`Section ${index + 1}`}
|
|
||||||
isActive={selectedBlockId === `section-${index}`}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={section.cardStyleClass || SECTION_CARD_STYLE_DEFAULT}>
|
|
||||||
<h3 className={section.titleStyleClass || SECTION_TITLE_STYLE_DEFAULT}>
|
|
||||||
{section.title}
|
|
||||||
</h3>
|
|
||||||
<p
|
|
||||||
className={section.descriptionStyleClass || SECTION_DESCRIPTION_STYLE_DEFAULT}
|
|
||||||
>
|
|
||||||
{section.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DesignerLayer pageTitle="About" selection={selection} designer={designer} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default About
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { BlogCategory, BlogPost } from '@/proxy/blog/blog'
|
||||||
import { blogService } from '@/services/blog.service'
|
import { blogService } from '@/services/blog.service'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { Button } from '@/components/ui'
|
import { Button } from '@/components/ui'
|
||||||
import { translateLabel } from './designer'
|
import { translateLabel } from './localization'
|
||||||
import { PublicPageLoader } from './shared'
|
import { PublicPageLoader } from './shared'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { blogService } from '@/services/blog.service'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { translateLabel } from './designer'
|
import { translateLabel } from './localization'
|
||||||
import { PublicPageLoader } from './shared'
|
import { PublicPageLoader } from './shared'
|
||||||
|
|
||||||
const CenteredMessage: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
const CenteredMessage: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
|
|
||||||
|
|
@ -1,608 +0,0 @@
|
||||||
import React, { useEffect, useMemo, useState } from 'react'
|
|
||||||
import {
|
|
||||||
FaPhone,
|
|
||||||
FaBuilding,
|
|
||||||
FaCalendarAlt,
|
|
||||||
FaCalendarCheck,
|
|
||||||
FaRegComment,
|
|
||||||
FaMapMarkerAlt,
|
|
||||||
FaEnvelope,
|
|
||||||
FaIdCard,
|
|
||||||
} from 'react-icons/fa'
|
|
||||||
import PageTitle from '@/components/shared/PageTitle'
|
|
||||||
import { ContactDto } from '@/proxy/contact/models'
|
|
||||||
import { getContact, saveContactPage } from '@/services/contact.service'
|
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
||||||
import { PUBLIC_PAGE_DESIGN } from '@/constants/permission.constant'
|
|
||||||
import {
|
|
||||||
DesignerLayer,
|
|
||||||
DesignerSelection,
|
|
||||||
SelectableBlock,
|
|
||||||
TranslateFn,
|
|
||||||
booleanField,
|
|
||||||
imageField,
|
|
||||||
resolveLocalizedValue,
|
|
||||||
styleField,
|
|
||||||
textAreaField,
|
|
||||||
textField,
|
|
||||||
translateLabel,
|
|
||||||
usePageDesigner,
|
|
||||||
} from './designer'
|
|
||||||
import { designerPageClass } from './pageStyles'
|
|
||||||
import { PublicPageLoader } from './shared'
|
|
||||||
|
|
||||||
interface ContactContent {
|
|
||||||
heroTitle: string
|
|
||||||
heroTitleKey: string
|
|
||||||
heroSectionStyleClass: string
|
|
||||||
heroSectionStyleClassKey: string
|
|
||||||
heroTitleStyleClass: string
|
|
||||||
heroTitleStyleClassKey: string
|
|
||||||
heroSubtitle: string
|
|
||||||
heroSubtitleKey: string
|
|
||||||
heroSubtitleStyleClass: string
|
|
||||||
heroSubtitleStyleClassKey: string
|
|
||||||
heroImage: string
|
|
||||||
heroImageKey: string
|
|
||||||
contactInfoTitle: string
|
|
||||||
contactInfoTitleKey: string
|
|
||||||
contactInfoCardStyleClass: string
|
|
||||||
contactInfoCardStyleClassKey: string
|
|
||||||
contactInfoTitleStyleClass: string
|
|
||||||
contactInfoTitleStyleClassKey: string
|
|
||||||
address: string
|
|
||||||
addressKey: string
|
|
||||||
phoneNumber: string
|
|
||||||
email: string
|
|
||||||
location: string
|
|
||||||
taxNumber: string
|
|
||||||
bankTitle: string
|
|
||||||
bankTitleKey: string
|
|
||||||
bankAccountHolder: string
|
|
||||||
bankBranch: string
|
|
||||||
bankAccountNumber: string
|
|
||||||
bankIban: string
|
|
||||||
bankStyleClass: string
|
|
||||||
workHoursTitle: string
|
|
||||||
workHoursTitleKey: string
|
|
||||||
workWeekday: string
|
|
||||||
workWeekdayKey: string
|
|
||||||
workWeekend: string
|
|
||||||
workWeekendKey: string
|
|
||||||
workWhatsapp: string
|
|
||||||
workWhatsappKey: string
|
|
||||||
workHoursStyleClass: string
|
|
||||||
mapTitle: string
|
|
||||||
mapTitleKey: string
|
|
||||||
mapSrc: string
|
|
||||||
mapWidth: string
|
|
||||||
mapHeight: string
|
|
||||||
mapAllowFullScreen: boolean
|
|
||||||
mapLoading: string
|
|
||||||
mapReferrerPolicy: string
|
|
||||||
mapContainerStyleClass: string
|
|
||||||
mapFrameStyleClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONTACT_HERO_TITLE_KEY = 'App.Contact'
|
|
||||||
const CONTACT_HERO_SUBTITLE_KEY = 'App.PublicContact.Subtitle'
|
|
||||||
const CONTACT_HERO_IMAGE_KEY = 'Public.contact.heroImage'
|
|
||||||
const CONTACT_HERO_IMAGE_DEFAULT =
|
|
||||||
'https://images.pexels.com/photos/3183171/pexels-photo-3183171.jpeg?auto=compress&cs=tinysrgb&w=1920'
|
|
||||||
const CONTACT_INFO_TITLE_KEY = 'Abp.Identity.User.UserInformation.ContactInformation'
|
|
||||||
const CONTACT_BANK_TITLE_KEY = 'App.ContactBank.BankTitle'
|
|
||||||
const CONTACT_WORK_HOURS_TITLE_KEY = 'App.Definitions.WorkHour'
|
|
||||||
const CONTACT_HERO_SECTION_STYLE_KEY = 'Public.contact.hero.sectionStyleClass'
|
|
||||||
const CONTACT_HERO_TITLE_STYLE_KEY = 'Public.contact.hero.titleStyleClass'
|
|
||||||
const CONTACT_HERO_SUBTITLE_STYLE_KEY = 'Public.contact.hero.subtitleStyleClass'
|
|
||||||
const CONTACT_INFO_CARD_STYLE_KEY = 'Public.contact.info.cardStyleClass'
|
|
||||||
const CONTACT_INFO_TITLE_STYLE_KEY = 'Public.contact.info.titleStyleClass'
|
|
||||||
|
|
||||||
const HERO_SECTION_STYLE_DEFAULT = 'relative bg-blue-900 py-12 text-white dark:bg-gray-950'
|
|
||||||
const HERO_TITLE_STYLE_DEFAULT = 'ml-4 mb-2 mt-3 text-5xl font-bold text-white'
|
|
||||||
const HERO_SUBTITLE_STYLE_DEFAULT = 'ml-4 max-w-3xl text-xl'
|
|
||||||
const CARD_STYLE_DEFAULT =
|
|
||||||
'rounded-xl bg-white p-8 shadow-lg dark:bg-gray-900 dark:shadow-gray-950/40'
|
|
||||||
const INFO_TITLE_STYLE_DEFAULT = 'mb-6 text-2xl font-bold text-gray-900 dark:text-gray-100'
|
|
||||||
const MAP_FRAME_STYLE_DEFAULT =
|
|
||||||
'aspect-w-16 aspect-h-9 overflow-hidden rounded-xl bg-gray-200 dark:bg-gray-800'
|
|
||||||
|
|
||||||
function buildContactContent(
|
|
||||||
contact: ContactDto | undefined,
|
|
||||||
translate: TranslateFn,
|
|
||||||
): ContactContent {
|
|
||||||
return {
|
|
||||||
heroTitle: resolveLocalizedValue(translate, CONTACT_HERO_TITLE_KEY, 'Contact'),
|
|
||||||
heroTitleKey: CONTACT_HERO_TITLE_KEY,
|
|
||||||
heroSectionStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
CONTACT_HERO_SECTION_STYLE_KEY,
|
|
||||||
HERO_SECTION_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
heroSectionStyleClassKey: CONTACT_HERO_SECTION_STYLE_KEY,
|
|
||||||
heroTitleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
CONTACT_HERO_TITLE_STYLE_KEY,
|
|
||||||
HERO_TITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
heroTitleStyleClassKey: CONTACT_HERO_TITLE_STYLE_KEY,
|
|
||||||
heroSubtitle: resolveLocalizedValue(translate, CONTACT_HERO_SUBTITLE_KEY),
|
|
||||||
heroSubtitleKey: CONTACT_HERO_SUBTITLE_KEY,
|
|
||||||
heroSubtitleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
CONTACT_HERO_SUBTITLE_STYLE_KEY,
|
|
||||||
HERO_SUBTITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
heroSubtitleStyleClassKey: CONTACT_HERO_SUBTITLE_STYLE_KEY,
|
|
||||||
heroImage: resolveLocalizedValue(translate, CONTACT_HERO_IMAGE_KEY, CONTACT_HERO_IMAGE_DEFAULT),
|
|
||||||
heroImageKey: CONTACT_HERO_IMAGE_KEY,
|
|
||||||
contactInfoTitle: resolveLocalizedValue(translate, CONTACT_INFO_TITLE_KEY),
|
|
||||||
contactInfoTitleKey: CONTACT_INFO_TITLE_KEY,
|
|
||||||
contactInfoCardStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
CONTACT_INFO_CARD_STYLE_KEY,
|
|
||||||
CARD_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
contactInfoCardStyleClassKey: CONTACT_INFO_CARD_STYLE_KEY,
|
|
||||||
contactInfoTitleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
CONTACT_INFO_TITLE_STYLE_KEY,
|
|
||||||
INFO_TITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
contactInfoTitleStyleClassKey: CONTACT_INFO_TITLE_STYLE_KEY,
|
|
||||||
address: resolveLocalizedValue(translate, contact?.address, ''),
|
|
||||||
addressKey: contact?.address || '',
|
|
||||||
phoneNumber: contact?.phoneNumber || '',
|
|
||||||
email: contact?.email || '',
|
|
||||||
location: contact?.location || '',
|
|
||||||
taxNumber: contact?.taxNumber || '',
|
|
||||||
bankTitle: resolveLocalizedValue(translate, CONTACT_BANK_TITLE_KEY),
|
|
||||||
bankTitleKey: CONTACT_BANK_TITLE_KEY,
|
|
||||||
bankAccountHolder: contact?.bankDto?.accountHolder || '',
|
|
||||||
bankBranch: contact?.bankDto?.branch || '',
|
|
||||||
bankAccountNumber: contact?.bankDto?.accountNumber || '',
|
|
||||||
bankIban: contact?.bankDto?.iban || '',
|
|
||||||
bankStyleClass: contact?.bankDto?.styleClass || CARD_STYLE_DEFAULT,
|
|
||||||
workHoursTitle: resolveLocalizedValue(translate, CONTACT_WORK_HOURS_TITLE_KEY),
|
|
||||||
workHoursTitleKey: CONTACT_WORK_HOURS_TITLE_KEY,
|
|
||||||
workWeekday: resolveLocalizedValue(translate, contact?.workHoursDto?.weekday, ''),
|
|
||||||
workWeekdayKey: contact?.workHoursDto?.weekday || '',
|
|
||||||
workWeekend: resolveLocalizedValue(translate, contact?.workHoursDto?.weekend, ''),
|
|
||||||
workWeekendKey: contact?.workHoursDto?.weekend || '',
|
|
||||||
workWhatsapp: resolveLocalizedValue(translate, contact?.workHoursDto?.whatsapp, ''),
|
|
||||||
workWhatsappKey: contact?.workHoursDto?.whatsapp || '',
|
|
||||||
workHoursStyleClass: contact?.workHoursDto?.styleClass || CARD_STYLE_DEFAULT,
|
|
||||||
mapTitle: resolveLocalizedValue(translate, contact?.mapDto?.title, ''),
|
|
||||||
mapTitleKey: contact?.mapDto?.title || '',
|
|
||||||
mapSrc: contact?.mapDto?.src || '',
|
|
||||||
mapWidth: contact?.mapDto?.width || '100%',
|
|
||||||
mapHeight: contact?.mapDto?.height || '700',
|
|
||||||
mapAllowFullScreen: contact?.mapDto?.allowFullScreen ?? true,
|
|
||||||
mapLoading: contact?.mapDto?.loading || 'lazy',
|
|
||||||
mapReferrerPolicy: contact?.mapDto?.referrerPolicy || 'no-referrer-when-downgrade',
|
|
||||||
mapContainerStyleClass: contact?.mapDto?.containerStyleClass || CARD_STYLE_DEFAULT,
|
|
||||||
mapFrameStyleClass: contact?.mapDto?.frameStyleClass || MAP_FRAME_STYLE_DEFAULT,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toSaveInput(content: ContactContent, cultureName: string) {
|
|
||||||
return {
|
|
||||||
cultureName,
|
|
||||||
heroTitleKey: content.heroTitleKey,
|
|
||||||
heroTitleValue: content.heroTitle,
|
|
||||||
heroSubtitleKey: content.heroSubtitleKey,
|
|
||||||
heroSubtitleValue: content.heroSubtitle,
|
|
||||||
heroImageKey: content.heroImageKey,
|
|
||||||
heroImageValue: content.heroImage,
|
|
||||||
contactInfoTitleKey: content.contactInfoTitleKey,
|
|
||||||
contactInfoTitleValue: content.contactInfoTitle,
|
|
||||||
addressKey: content.addressKey,
|
|
||||||
addressValue: content.address,
|
|
||||||
phoneNumber: content.phoneNumber,
|
|
||||||
email: content.email,
|
|
||||||
location: content.location,
|
|
||||||
taxNumber: content.taxNumber,
|
|
||||||
bankTitleKey: content.bankTitleKey,
|
|
||||||
bankTitleValue: content.bankTitle,
|
|
||||||
bankAccountHolder: content.bankAccountHolder,
|
|
||||||
bankBranch: content.bankBranch,
|
|
||||||
bankAccountNumber: content.bankAccountNumber,
|
|
||||||
bankIban: content.bankIban,
|
|
||||||
bankStyleClass: content.bankStyleClass,
|
|
||||||
workHoursTitleKey: content.workHoursTitleKey,
|
|
||||||
workHoursTitleValue: content.workHoursTitle,
|
|
||||||
workWeekdayKey: content.workWeekdayKey,
|
|
||||||
workWeekdayValue: content.workWeekday,
|
|
||||||
workWeekendKey: content.workWeekendKey,
|
|
||||||
workWeekendValue: content.workWeekend,
|
|
||||||
workWhatsappKey: content.workWhatsappKey,
|
|
||||||
workWhatsappValue: content.workWhatsapp,
|
|
||||||
workHoursStyleClass: content.workHoursStyleClass,
|
|
||||||
mapTitleKey: content.mapTitleKey,
|
|
||||||
mapTitleValue: content.mapTitle,
|
|
||||||
mapSrc: content.mapSrc,
|
|
||||||
mapWidth: content.mapWidth,
|
|
||||||
mapHeight: content.mapHeight,
|
|
||||||
mapAllowFullScreen: content.mapAllowFullScreen,
|
|
||||||
mapLoading: content.mapLoading,
|
|
||||||
mapReferrerPolicy: content.mapReferrerPolicy,
|
|
||||||
mapContainerStyleClass: content.mapContainerStyleClass,
|
|
||||||
mapFrameStyleClass: content.mapFrameStyleClass,
|
|
||||||
styleTexts: [
|
|
||||||
{ key: content.heroSectionStyleClassKey, value: content.heroSectionStyleClass },
|
|
||||||
{ key: content.heroTitleStyleClassKey, value: content.heroTitleStyleClass },
|
|
||||||
{ key: content.heroSubtitleStyleClassKey, value: content.heroSubtitleStyleClass },
|
|
||||||
{ key: content.contactInfoCardStyleClassKey, value: content.contactInfoCardStyleClass },
|
|
||||||
{ key: content.contactInfoTitleStyleClassKey, value: content.contactInfoTitleStyleClass },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const InfoRow: React.FC<{ icon: React.ReactNode; children: React.ReactNode }> = ({
|
|
||||||
icon,
|
|
||||||
children,
|
|
||||||
}) => (
|
|
||||||
<div className="flex items-start space-x-2">
|
|
||||||
<span className="mt-1 flex-shrink-0 text-blue-600 dark:text-blue-400">{icon}</span>
|
|
||||||
<p className="text-gray-600 dark:text-gray-300">{children}</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const Contact: React.FC = () => {
|
|
||||||
const { translate } = useLocalization()
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [contact, setContact] = useState<ContactDto>()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchContact = async () => {
|
|
||||||
setLoading(true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await getContact()
|
|
||||||
setContact(result.data)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Contact alinirken hata olustu:', error)
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchContact()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const initialContent = useMemo(
|
|
||||||
() => (loading ? null : buildContactContent(contact, translate)),
|
|
||||||
[contact, loading, translate],
|
|
||||||
)
|
|
||||||
|
|
||||||
const designer = usePageDesigner<ContactContent>({
|
|
||||||
pageKey: 'Contact',
|
|
||||||
permission: PUBLIC_PAGE_DESIGN.CONTACT,
|
|
||||||
initialContent,
|
|
||||||
onSave: (content, cultureName) => saveContactPage(toSaveInput(content, cultureName)),
|
|
||||||
})
|
|
||||||
|
|
||||||
const { content, isDesignMode, isPanelVisible, selectedBlockId, selectBlock } = designer
|
|
||||||
|
|
||||||
const selection: DesignerSelection | null = useMemo(() => {
|
|
||||||
if (!content || !selectedBlockId) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'hero') {
|
|
||||||
return {
|
|
||||||
id: 'hero',
|
|
||||||
title: 'Public.contact.hero.*',
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc4'),
|
|
||||||
fields: [
|
|
||||||
textField('heroTitle', content.heroTitleKey, content.heroTitle),
|
|
||||||
textAreaField('heroSubtitle', content.heroSubtitleKey, content.heroSubtitle),
|
|
||||||
imageField('heroImage', content.heroImageKey, content.heroImage, { group: 'media' }),
|
|
||||||
styleField(
|
|
||||||
'heroTitleStyleClass',
|
|
||||||
content.heroTitleStyleClassKey,
|
|
||||||
content.heroTitleStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'heroSubtitleStyleClass',
|
|
||||||
content.heroSubtitleStyleClassKey,
|
|
||||||
content.heroSubtitleStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'heroSectionStyleClass',
|
|
||||||
content.heroSectionStyleClassKey,
|
|
||||||
content.heroSectionStyleClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'contact-info') {
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: content.contactInfoTitleKey,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc5'),
|
|
||||||
fields: [
|
|
||||||
textField('contactInfoTitle', content.contactInfoTitleKey, content.contactInfoTitle),
|
|
||||||
textAreaField('address', content.addressKey || 'Public.contact.address', content.address),
|
|
||||||
textField('phoneNumber', 'Public.contact.phone', content.phoneNumber),
|
|
||||||
textField('email', 'Public.contact.email', content.email),
|
|
||||||
textField('location', 'Public.contact.location.plain', content.location),
|
|
||||||
textField('taxNumber', 'App.PublicContact.TaxNumber', content.taxNumber),
|
|
||||||
styleField(
|
|
||||||
'contactInfoTitleStyleClass',
|
|
||||||
content.contactInfoTitleStyleClassKey,
|
|
||||||
content.contactInfoTitleStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'contactInfoCardStyleClass',
|
|
||||||
content.contactInfoCardStyleClassKey,
|
|
||||||
content.contactInfoCardStyleClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'bank') {
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: content.bankTitleKey,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc6'),
|
|
||||||
fields: [
|
|
||||||
textField('bankTitle', content.bankTitleKey, content.bankTitle),
|
|
||||||
textField(
|
|
||||||
'bankAccountHolder',
|
|
||||||
'Public.contact.bank.accountHolder',
|
|
||||||
content.bankAccountHolder,
|
|
||||||
),
|
|
||||||
textField('bankBranch', 'Public.contact.bank.branch', content.bankBranch),
|
|
||||||
textField(
|
|
||||||
'bankAccountNumber',
|
|
||||||
'Public.contact.bank.accountNumber',
|
|
||||||
content.bankAccountNumber,
|
|
||||||
),
|
|
||||||
textField('bankIban', 'Public.contact.bank.iban', content.bankIban),
|
|
||||||
styleField('bankStyleClass', 'Public.contact.bank.styleClass', content.bankStyleClass),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'work-hours') {
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: content.workHoursTitleKey,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc7'),
|
|
||||||
fields: [
|
|
||||||
textField('workHoursTitle', content.workHoursTitleKey, content.workHoursTitle),
|
|
||||||
textField('workWeekday', content.workWeekdayKey || 'weekday', content.workWeekday),
|
|
||||||
textField('workWeekend', content.workWeekendKey || 'weekend', content.workWeekend),
|
|
||||||
textField('workWhatsapp', content.workWhatsappKey || 'whatsapp', content.workWhatsapp),
|
|
||||||
styleField(
|
|
||||||
'workHoursStyleClass',
|
|
||||||
'Public.contact.workHours.styleClass',
|
|
||||||
content.workHoursStyleClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'map') {
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: content.mapTitleKey || 'Public.contact.map',
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc8'),
|
|
||||||
fields: [
|
|
||||||
textField(
|
|
||||||
'mapTitle',
|
|
||||||
content.mapTitleKey || 'Public.contact.map.title',
|
|
||||||
content.mapTitle,
|
|
||||||
),
|
|
||||||
textAreaField('mapSrc', 'Public.contact.map.src', content.mapSrc, { group: 'media' }),
|
|
||||||
textField('mapWidth', 'Public.contact.map.width', content.mapWidth, {
|
|
||||||
group: 'advanced',
|
|
||||||
}),
|
|
||||||
textField('mapHeight', 'Public.contact.map.height', content.mapHeight, {
|
|
||||||
group: 'advanced',
|
|
||||||
}),
|
|
||||||
booleanField(
|
|
||||||
'mapAllowFullScreen',
|
|
||||||
'Public.contact.map.allowFullScreen',
|
|
||||||
content.mapAllowFullScreen,
|
|
||||||
{ group: 'advanced' },
|
|
||||||
),
|
|
||||||
textField('mapLoading', 'Public.contact.map.loading', content.mapLoading, {
|
|
||||||
group: 'advanced',
|
|
||||||
placeholder: 'lazy | eager',
|
|
||||||
}),
|
|
||||||
textField(
|
|
||||||
'mapReferrerPolicy',
|
|
||||||
'Public.contact.map.referrerPolicy',
|
|
||||||
content.mapReferrerPolicy,
|
|
||||||
{
|
|
||||||
group: 'advanced',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'mapContainerStyleClass',
|
|
||||||
'Public.contact.map.containerStyleClass',
|
|
||||||
content.mapContainerStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'mapFrameStyleClass',
|
|
||||||
'Public.contact.map.frameStyleClass',
|
|
||||||
content.mapFrameStyleClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}, [content, selectedBlockId, translate])
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <PublicPageLoader />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={designerPageClass(isDesignMode, isPanelVisible)}>
|
|
||||||
<PageTitle title={translate('::App.Contact')} />
|
|
||||||
|
|
||||||
<SelectableBlock
|
|
||||||
id="hero"
|
|
||||||
label="Hero"
|
|
||||||
isActive={selectedBlockId === 'hero'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={content?.heroSectionStyleClass || HERO_SECTION_STYLE_DEFAULT}>
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 opacity-20 dark:opacity-35"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url("${content?.heroImage || CONTACT_HERO_IMAGE_DEFAULT}")`,
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'center',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-blue-950/25 dark:bg-gray-950/45" />
|
|
||||||
<div className="container relative mx-auto pt-20">
|
|
||||||
<h1 className={content?.heroTitleStyleClass || HERO_TITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.heroTitle}
|
|
||||||
</h1>
|
|
||||||
<p className={content?.heroSubtitleStyleClass || HERO_SUBTITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.heroSubtitle}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
|
|
||||||
<div className="py-6">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<div className="grid grid-cols-1 gap-12 lg:grid-cols-2">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<SelectableBlock
|
|
||||||
id="contact-info"
|
|
||||||
label="Contact info"
|
|
||||||
isActive={selectedBlockId === 'contact-info'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={content?.contactInfoCardStyleClass || CARD_STYLE_DEFAULT}>
|
|
||||||
<h2 className={content?.contactInfoTitleStyleClass || INFO_TITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.contactInfoTitle}
|
|
||||||
</h2>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<InfoRow icon={<FaMapMarkerAlt className="h-5 w-5" />}>
|
|
||||||
{content?.address}
|
|
||||||
</InfoRow>
|
|
||||||
<InfoRow icon={<FaPhone className="h-5 w-5" />}>{content?.phoneNumber}</InfoRow>
|
|
||||||
<InfoRow icon={<FaEnvelope className="h-5 w-5" />}>
|
|
||||||
<a
|
|
||||||
href={`mailto:${content?.email}`}
|
|
||||||
className="text-blue-600 hover:underline dark:text-blue-400"
|
|
||||||
>
|
|
||||||
{content?.email}
|
|
||||||
</a>
|
|
||||||
</InfoRow>
|
|
||||||
<InfoRow icon={<FaBuilding className="h-5 w-5" />}>{content?.location}</InfoRow>
|
|
||||||
<InfoRow icon={<FaIdCard className="h-5 w-5" />}>{content?.taxNumber}</InfoRow>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
|
|
||||||
<SelectableBlock
|
|
||||||
id="bank"
|
|
||||||
label="Bank"
|
|
||||||
isActive={selectedBlockId === 'bank'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={content?.bankStyleClass || CARD_STYLE_DEFAULT}>
|
|
||||||
<h2 className="mb-6 text-2xl font-bold text-gray-900 dark:text-gray-100">
|
|
||||||
{content?.bankTitle}
|
|
||||||
</h2>
|
|
||||||
<img
|
|
||||||
src="/img/enpara.svg"
|
|
||||||
alt={content?.bankAccountHolder || 'Bank'}
|
|
||||||
className="mb-2 w-24 object-contain"
|
|
||||||
/>
|
|
||||||
<h3 className="mb-1 font-semibold text-gray-900 dark:text-gray-100">
|
|
||||||
{content?.bankAccountHolder}
|
|
||||||
</h3>
|
|
||||||
<p className="mb-1 ml-1 text-gray-600 dark:text-gray-300">
|
|
||||||
{content?.bankBranch}
|
|
||||||
</p>
|
|
||||||
<p className="mb-1 ml-1 text-gray-600 dark:text-gray-300">
|
|
||||||
{content?.bankAccountNumber}
|
|
||||||
</p>
|
|
||||||
<p className="mb-1 ml-1 text-gray-600 dark:text-gray-300">{content?.bankIban}</p>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
|
|
||||||
<SelectableBlock
|
|
||||||
id="work-hours"
|
|
||||||
label="Work hours"
|
|
||||||
isActive={selectedBlockId === 'work-hours'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={content?.workHoursStyleClass || CARD_STYLE_DEFAULT}>
|
|
||||||
<h2 className="mb-6 text-2xl font-bold text-gray-900 dark:text-gray-100">
|
|
||||||
{content?.workHoursTitle}
|
|
||||||
</h2>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<FaCalendarAlt className="h-5 w-5 text-blue-500 dark:text-blue-400" />
|
|
||||||
<p className="text-gray-600 dark:text-gray-300">{content?.workWeekday}</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<FaCalendarCheck className="h-5 w-5 text-blue-500 dark:text-blue-400" />
|
|
||||||
<p className="text-gray-600 dark:text-gray-300">{content?.workWeekend}</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<FaRegComment className="h-5 w-5 text-green-500 dark:text-green-400" />
|
|
||||||
<p className="text-gray-600 dark:text-gray-300">{content?.workWhatsapp}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SelectableBlock
|
|
||||||
id="map"
|
|
||||||
label="Map"
|
|
||||||
isActive={selectedBlockId === 'map'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={content?.mapContainerStyleClass || CARD_STYLE_DEFAULT}>
|
|
||||||
<h2 className="mb-2 text-center text-2xl font-bold text-gray-900 dark:text-gray-100">
|
|
||||||
{content?.mapTitle}
|
|
||||||
</h2>
|
|
||||||
<div className={content?.mapFrameStyleClass || MAP_FRAME_STYLE_DEFAULT}>
|
|
||||||
{content?.mapSrc && (
|
|
||||||
<iframe
|
|
||||||
title={content.mapTitle || 'map'}
|
|
||||||
src={content.mapSrc}
|
|
||||||
width={content.mapWidth}
|
|
||||||
height={content.mapHeight}
|
|
||||||
style={{ border: 0 }}
|
|
||||||
allowFullScreen={content.mapAllowFullScreen}
|
|
||||||
loading={content.mapLoading === 'eager' ? 'eager' : 'lazy'}
|
|
||||||
referrerPolicy={
|
|
||||||
content.mapReferrerPolicy as React.HTMLAttributeReferrerPolicy | undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DesignerLayer pageTitle="Contact" selection={selection} designer={designer} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Contact
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { createDemoAsync } from '@/services/demo.service'
|
import { createDemoAsync } from '@/services/demo.service'
|
||||||
import { DemoDto } from '@/proxy/demo/models'
|
import { DemoDto } from '@/proxy/demo/models'
|
||||||
import { Button, Notification, toast } from '@/components/ui'
|
import { Button, Notification, toast } from '@/components/ui'
|
||||||
import { translateLabel } from './designer'
|
import { translateLabel } from './localization'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
|
||||||
interface DemoModalProps {
|
interface DemoModalProps {
|
||||||
|
|
|
||||||
|
|
@ -1,904 +0,0 @@
|
||||||
import React, { useEffect, useMemo, useState } from 'react'
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import { FaArrowRight, FaChevronLeft, FaChevronRight } from 'react-icons/fa'
|
|
||||||
import PageTitle from '@/components/shared/PageTitle'
|
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
|
||||||
import { PUBLIC_PAGE_DESIGN } from '@/constants/permission.constant'
|
|
||||||
import { Button } from '@/components/ui'
|
|
||||||
import { useOptionalDemo } from '@/contexts/DemoContext'
|
|
||||||
import { getHome, HomeDto, saveHomePage } from '@/services/home.service'
|
|
||||||
import {
|
|
||||||
DesignerLayer,
|
|
||||||
DesignerField,
|
|
||||||
DesignerSelection,
|
|
||||||
SelectableBlock,
|
|
||||||
TranslateFn,
|
|
||||||
iconField,
|
|
||||||
imageField,
|
|
||||||
resolveLocalizedValue,
|
|
||||||
styleField,
|
|
||||||
textAreaField,
|
|
||||||
textField,
|
|
||||||
translateLabel,
|
|
||||||
usePageDesigner,
|
|
||||||
} from './designer'
|
|
||||||
import { designerPageClass } from './pageStyles'
|
|
||||||
import { PageIcon, PublicPageLoader } from './shared'
|
|
||||||
|
|
||||||
interface HomeSlideServiceContent {
|
|
||||||
icon: string
|
|
||||||
title: string
|
|
||||||
titleKey: string
|
|
||||||
description: string
|
|
||||||
descriptionKey: string
|
|
||||||
styleClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HomeSlideContent {
|
|
||||||
title: string
|
|
||||||
titleKey: string
|
|
||||||
subtitle: string
|
|
||||||
subtitleKey: string
|
|
||||||
styleClass: string
|
|
||||||
services: HomeSlideServiceContent[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HomeCardContent {
|
|
||||||
icon: string
|
|
||||||
title: string
|
|
||||||
titleKey: string
|
|
||||||
description: string
|
|
||||||
descriptionKey: string
|
|
||||||
styleClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HomeSolutionContent extends HomeCardContent {
|
|
||||||
colorClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HomeContent {
|
|
||||||
heroBackgroundImage: string
|
|
||||||
heroBackgroundImageKey: string
|
|
||||||
heroPrimaryCtaLabel: string
|
|
||||||
heroPrimaryCtaKey: string
|
|
||||||
heroPrimaryCtaStyle: string
|
|
||||||
heroPrimaryCtaStyleKey: string
|
|
||||||
heroSecondaryCtaLabel: string
|
|
||||||
heroSecondaryCtaKey: string
|
|
||||||
heroSecondaryCtaStyle: string
|
|
||||||
heroSecondaryCtaStyleKey: string
|
|
||||||
featuresTitle: string
|
|
||||||
featuresTitleKey: string
|
|
||||||
featuresTitleStyle: string
|
|
||||||
featuresTitleStyleKey: string
|
|
||||||
featuresSubtitle: string
|
|
||||||
featuresSubtitleKey: string
|
|
||||||
featuresSubtitleStyle: string
|
|
||||||
featuresSubtitleStyleKey: string
|
|
||||||
solutionsTitle: string
|
|
||||||
solutionsTitleKey: string
|
|
||||||
solutionsTitleStyle: string
|
|
||||||
solutionsTitleStyleKey: string
|
|
||||||
solutionsSubtitle: string
|
|
||||||
solutionsSubtitleKey: string
|
|
||||||
solutionsSubtitleStyle: string
|
|
||||||
solutionsSubtitleStyleKey: string
|
|
||||||
ctaTitle: string
|
|
||||||
ctaTitleKey: string
|
|
||||||
ctaTitleStyle: string
|
|
||||||
ctaTitleStyleKey: string
|
|
||||||
ctaSubtitle: string
|
|
||||||
ctaSubtitleKey: string
|
|
||||||
ctaSubtitleStyle: string
|
|
||||||
ctaSubtitleStyleKey: string
|
|
||||||
ctaButtonLabel: string
|
|
||||||
ctaButtonLabelKey: string
|
|
||||||
ctaButtonStyle: string
|
|
||||||
ctaButtonStyleKey: string
|
|
||||||
slides: HomeSlideContent[]
|
|
||||||
features: HomeCardContent[]
|
|
||||||
solutions: HomeSolutionContent[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const SLIDE_INTERVAL_MS = 10000
|
|
||||||
|
|
||||||
const HOME_HERO_DEFAULT_IMAGE =
|
|
||||||
'https://images.pexels.com/photos/3183150/pexels-photo-3183150.jpeg?auto=compress&cs=tinysrgb&w=1920'
|
|
||||||
const HOME_HERO_PRIMARY_CTA_STYLE_KEY = 'Public.home.hero.primaryCta.style'
|
|
||||||
const HOME_HERO_SECONDARY_CTA_STYLE_KEY = 'Public.home.hero.secondaryCta.style'
|
|
||||||
const HOME_FEATURES_TITLE_STYLE_KEY = 'Public.home.features.title.style'
|
|
||||||
const HOME_FEATURES_SUBTITLE_STYLE_KEY = 'Public.home.features.subtitle.style'
|
|
||||||
const HOME_SOLUTIONS_TITLE_STYLE_KEY = 'Public.home.solutions.title.style'
|
|
||||||
const HOME_SOLUTIONS_SUBTITLE_STYLE_KEY = 'Public.home.solutions.subtitle.style'
|
|
||||||
const HOME_CTA_TITLE_STYLE_KEY = 'Public.home.cta.title.style'
|
|
||||||
const HOME_CTA_SUBTITLE_STYLE_KEY = 'Public.home.cta.subtitle.style'
|
|
||||||
const HOME_CTA_BUTTON_STYLE_KEY = 'Public.home.cta.button.style'
|
|
||||||
|
|
||||||
const HERO_PRIMARY_CTA_STYLE_DEFAULT =
|
|
||||||
'inline-flex items-center justify-center px-8 py-4 bg-gradient-to-r from-blue-500 to-purple-500 hover:from-blue-600 hover:to-purple-600 text-white rounded-lg font-semibold transition-all transform hover:scale-105'
|
|
||||||
const HERO_SECONDARY_CTA_STYLE_DEFAULT =
|
|
||||||
'inline-flex items-center justify-center px-8 py-4 bg-white/10 hover:bg-white/20 text-white rounded-lg font-semibold backdrop-blur-sm transition-all transform hover:scale-105'
|
|
||||||
const FEATURES_TITLE_STYLE_DEFAULT = 'text-4xl font-bold text-gray-900 dark:text-gray-100 mb-4'
|
|
||||||
const FEATURES_SUBTITLE_STYLE_DEFAULT = 'text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto'
|
|
||||||
const SOLUTIONS_TITLE_STYLE_DEFAULT = 'text-4xl font-bold text-gray-900 dark:text-gray-100 mb-4'
|
|
||||||
const SOLUTIONS_SUBTITLE_STYLE_DEFAULT =
|
|
||||||
'text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto'
|
|
||||||
const CTA_TITLE_STYLE_DEFAULT = 'text-3xl font-bold text-white mb-4'
|
|
||||||
const CTA_SUBTITLE_STYLE_DEFAULT = 'text-white text-lg mb-8'
|
|
||||||
const CTA_BUTTON_STYLE_DEFAULT =
|
|
||||||
'inline-block bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-blue-50 transition-colors'
|
|
||||||
const SLIDE_STYLE_DEFAULT = 'max-w-4xl mx-auto text-center'
|
|
||||||
const SLIDE_SERVICE_STYLE_DEFAULT =
|
|
||||||
'bg-white/5 backdrop-blur-sm rounded-2xl p-8 text-center hover:scale-105 hover:bg-white/10 transition-all'
|
|
||||||
const FEATURE_CARD_STYLE_DEFAULT =
|
|
||||||
'p-8 bg-white dark:bg-gray-900 rounded-xl shadow-lg hover:shadow-xl transition-shadow dark:shadow-gray-950/40'
|
|
||||||
const SOLUTION_CARD_STYLE_DEFAULT = 'p-8 h-full rounded-2xl'
|
|
||||||
|
|
||||||
function buildHomeContent(home: HomeDto | undefined, translate: TranslateFn): HomeContent {
|
|
||||||
return {
|
|
||||||
heroBackgroundImage: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
home?.heroBackgroundImageKey,
|
|
||||||
HOME_HERO_DEFAULT_IMAGE,
|
|
||||||
),
|
|
||||||
heroBackgroundImageKey: home?.heroBackgroundImageKey || '',
|
|
||||||
heroPrimaryCtaLabel: resolveLocalizedValue(translate, home?.heroPrimaryCtaKey, ''),
|
|
||||||
heroPrimaryCtaKey: home?.heroPrimaryCtaKey || '',
|
|
||||||
heroPrimaryCtaStyle: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
HOME_HERO_PRIMARY_CTA_STYLE_KEY,
|
|
||||||
HERO_PRIMARY_CTA_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
heroPrimaryCtaStyleKey: HOME_HERO_PRIMARY_CTA_STYLE_KEY,
|
|
||||||
heroSecondaryCtaLabel: resolveLocalizedValue(translate, home?.heroSecondaryCtaKey, ''),
|
|
||||||
heroSecondaryCtaKey: home?.heroSecondaryCtaKey || '',
|
|
||||||
heroSecondaryCtaStyle: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
HOME_HERO_SECONDARY_CTA_STYLE_KEY,
|
|
||||||
HERO_SECONDARY_CTA_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
heroSecondaryCtaStyleKey: HOME_HERO_SECONDARY_CTA_STYLE_KEY,
|
|
||||||
featuresTitle: resolveLocalizedValue(translate, home?.featuresTitleKey, ''),
|
|
||||||
featuresTitleKey: home?.featuresTitleKey || '',
|
|
||||||
featuresTitleStyle: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
HOME_FEATURES_TITLE_STYLE_KEY,
|
|
||||||
FEATURES_TITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
featuresTitleStyleKey: HOME_FEATURES_TITLE_STYLE_KEY,
|
|
||||||
featuresSubtitle: resolveLocalizedValue(translate, home?.featuresSubtitleKey, ''),
|
|
||||||
featuresSubtitleKey: home?.featuresSubtitleKey || '',
|
|
||||||
featuresSubtitleStyle: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
HOME_FEATURES_SUBTITLE_STYLE_KEY,
|
|
||||||
FEATURES_SUBTITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
featuresSubtitleStyleKey: HOME_FEATURES_SUBTITLE_STYLE_KEY,
|
|
||||||
solutionsTitle: resolveLocalizedValue(translate, home?.solutionsTitleKey, ''),
|
|
||||||
solutionsTitleKey: home?.solutionsTitleKey || '',
|
|
||||||
solutionsTitleStyle: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
HOME_SOLUTIONS_TITLE_STYLE_KEY,
|
|
||||||
SOLUTIONS_TITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
solutionsTitleStyleKey: HOME_SOLUTIONS_TITLE_STYLE_KEY,
|
|
||||||
solutionsSubtitle: resolveLocalizedValue(translate, home?.solutionsSubtitleKey, ''),
|
|
||||||
solutionsSubtitleKey: home?.solutionsSubtitleKey || '',
|
|
||||||
solutionsSubtitleStyle: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
HOME_SOLUTIONS_SUBTITLE_STYLE_KEY,
|
|
||||||
SOLUTIONS_SUBTITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
solutionsSubtitleStyleKey: HOME_SOLUTIONS_SUBTITLE_STYLE_KEY,
|
|
||||||
ctaTitle: resolveLocalizedValue(translate, home?.ctaTitleKey, ''),
|
|
||||||
ctaTitleKey: home?.ctaTitleKey || '',
|
|
||||||
ctaTitleStyle: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
HOME_CTA_TITLE_STYLE_KEY,
|
|
||||||
CTA_TITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
ctaTitleStyleKey: HOME_CTA_TITLE_STYLE_KEY,
|
|
||||||
ctaSubtitle: resolveLocalizedValue(translate, home?.ctaSubtitleKey, ''),
|
|
||||||
ctaSubtitleKey: home?.ctaSubtitleKey || '',
|
|
||||||
ctaSubtitleStyle: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
HOME_CTA_SUBTITLE_STYLE_KEY,
|
|
||||||
CTA_SUBTITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
ctaSubtitleStyleKey: HOME_CTA_SUBTITLE_STYLE_KEY,
|
|
||||||
ctaButtonLabel: resolveLocalizedValue(translate, home?.ctaButtonLabelKey, ''),
|
|
||||||
ctaButtonLabelKey: home?.ctaButtonLabelKey || '',
|
|
||||||
ctaButtonStyle: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
HOME_CTA_BUTTON_STYLE_KEY,
|
|
||||||
CTA_BUTTON_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
ctaButtonStyleKey: HOME_CTA_BUTTON_STYLE_KEY,
|
|
||||||
slides: (home?.slidesDto ?? []).map((slide) => ({
|
|
||||||
title: resolveLocalizedValue(translate, slide.titleKey, slide.titleKey),
|
|
||||||
titleKey: slide.titleKey || '',
|
|
||||||
subtitle: resolveLocalizedValue(translate, slide.subtitleKey, slide.subtitleKey),
|
|
||||||
subtitleKey: slide.subtitleKey || '',
|
|
||||||
styleClass: slide.styleClass || SLIDE_STYLE_DEFAULT,
|
|
||||||
services: (slide.services ?? []).map((service) => ({
|
|
||||||
icon: service.icon || '',
|
|
||||||
title: resolveLocalizedValue(translate, service.titleKey, service.titleKey),
|
|
||||||
titleKey: service.titleKey || '',
|
|
||||||
description: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
service.descriptionKey,
|
|
||||||
service.descriptionKey,
|
|
||||||
),
|
|
||||||
descriptionKey: service.descriptionKey || '',
|
|
||||||
styleClass: service.styleClass || SLIDE_SERVICE_STYLE_DEFAULT,
|
|
||||||
})),
|
|
||||||
})),
|
|
||||||
features: (home?.featuresDto ?? []).map((feature) => ({
|
|
||||||
icon: feature.icon || '',
|
|
||||||
title: resolveLocalizedValue(translate, feature.titleKey, feature.titleKey),
|
|
||||||
titleKey: feature.titleKey || '',
|
|
||||||
description: resolveLocalizedValue(translate, feature.descriptionKey, feature.descriptionKey),
|
|
||||||
descriptionKey: feature.descriptionKey || '',
|
|
||||||
styleClass: feature.styleClass || FEATURE_CARD_STYLE_DEFAULT,
|
|
||||||
})),
|
|
||||||
solutions: (home?.solutionsDto ?? []).map((solution) => ({
|
|
||||||
icon: solution.icon || '',
|
|
||||||
colorClass: solution.colorClass || '',
|
|
||||||
title: resolveLocalizedValue(translate, solution.titleKey, solution.titleKey),
|
|
||||||
titleKey: solution.titleKey || '',
|
|
||||||
description: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
solution.descriptionKey,
|
|
||||||
solution.descriptionKey,
|
|
||||||
),
|
|
||||||
descriptionKey: solution.descriptionKey || '',
|
|
||||||
styleClass: solution.styleClass || SOLUTION_CARD_STYLE_DEFAULT,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toSaveInput(content: HomeContent, cultureName: string) {
|
|
||||||
return {
|
|
||||||
cultureName,
|
|
||||||
heroBackgroundImageKey: content.heroBackgroundImageKey,
|
|
||||||
heroBackgroundImageValue: content.heroBackgroundImage,
|
|
||||||
heroPrimaryCtaKey: content.heroPrimaryCtaKey,
|
|
||||||
heroPrimaryCtaValue: content.heroPrimaryCtaLabel,
|
|
||||||
heroPrimaryCtaStyleKey: content.heroPrimaryCtaStyleKey,
|
|
||||||
heroPrimaryCtaStyleValue: content.heroPrimaryCtaStyle,
|
|
||||||
heroSecondaryCtaKey: content.heroSecondaryCtaKey,
|
|
||||||
heroSecondaryCtaValue: content.heroSecondaryCtaLabel,
|
|
||||||
heroSecondaryCtaStyleKey: content.heroSecondaryCtaStyleKey,
|
|
||||||
heroSecondaryCtaStyleValue: content.heroSecondaryCtaStyle,
|
|
||||||
featuresTitleKey: content.featuresTitleKey,
|
|
||||||
featuresTitleValue: content.featuresTitle,
|
|
||||||
featuresTitleStyleKey: content.featuresTitleStyleKey,
|
|
||||||
featuresTitleStyleValue: content.featuresTitleStyle,
|
|
||||||
featuresSubtitleKey: content.featuresSubtitleKey,
|
|
||||||
featuresSubtitleValue: content.featuresSubtitle,
|
|
||||||
featuresSubtitleStyleKey: content.featuresSubtitleStyleKey,
|
|
||||||
featuresSubtitleStyleValue: content.featuresSubtitleStyle,
|
|
||||||
solutionsTitleKey: content.solutionsTitleKey,
|
|
||||||
solutionsTitleValue: content.solutionsTitle,
|
|
||||||
solutionsTitleStyleKey: content.solutionsTitleStyleKey,
|
|
||||||
solutionsTitleStyleValue: content.solutionsTitleStyle,
|
|
||||||
solutionsSubtitleKey: content.solutionsSubtitleKey,
|
|
||||||
solutionsSubtitleValue: content.solutionsSubtitle,
|
|
||||||
solutionsSubtitleStyleKey: content.solutionsSubtitleStyleKey,
|
|
||||||
solutionsSubtitleStyleValue: content.solutionsSubtitleStyle,
|
|
||||||
ctaTitleKey: content.ctaTitleKey,
|
|
||||||
ctaTitleValue: content.ctaTitle,
|
|
||||||
ctaTitleStyleKey: content.ctaTitleStyleKey,
|
|
||||||
ctaTitleStyleValue: content.ctaTitleStyle,
|
|
||||||
ctaSubtitleKey: content.ctaSubtitleKey,
|
|
||||||
ctaSubtitleValue: content.ctaSubtitle,
|
|
||||||
ctaSubtitleStyleKey: content.ctaSubtitleStyleKey,
|
|
||||||
ctaSubtitleStyleValue: content.ctaSubtitleStyle,
|
|
||||||
ctaButtonLabelKey: content.ctaButtonLabelKey,
|
|
||||||
ctaButtonLabelValue: content.ctaButtonLabel,
|
|
||||||
ctaButtonStyleKey: content.ctaButtonStyleKey,
|
|
||||||
ctaButtonStyleValue: content.ctaButtonStyle,
|
|
||||||
slides: content.slides.map((slide) => ({
|
|
||||||
titleKey: slide.titleKey,
|
|
||||||
titleValue: slide.title,
|
|
||||||
subtitleKey: slide.subtitleKey,
|
|
||||||
subtitleValue: slide.subtitle,
|
|
||||||
styleClass: slide.styleClass,
|
|
||||||
services: slide.services.map((service) => ({
|
|
||||||
icon: service.icon,
|
|
||||||
titleKey: service.titleKey,
|
|
||||||
titleValue: service.title,
|
|
||||||
descriptionKey: service.descriptionKey,
|
|
||||||
descriptionValue: service.description,
|
|
||||||
styleClass: service.styleClass,
|
|
||||||
})),
|
|
||||||
})),
|
|
||||||
features: content.features.map((feature) => ({
|
|
||||||
icon: feature.icon,
|
|
||||||
titleKey: feature.titleKey,
|
|
||||||
titleValue: feature.title,
|
|
||||||
descriptionKey: feature.descriptionKey,
|
|
||||||
descriptionValue: feature.description,
|
|
||||||
styleClass: feature.styleClass,
|
|
||||||
})),
|
|
||||||
solutions: content.solutions.map((solution) => ({
|
|
||||||
icon: solution.icon,
|
|
||||||
colorClass: solution.colorClass,
|
|
||||||
titleKey: solution.titleKey,
|
|
||||||
titleValue: solution.title,
|
|
||||||
descriptionKey: solution.descriptionKey,
|
|
||||||
descriptionValue: solution.description,
|
|
||||||
styleClass: solution.styleClass,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cardFields(
|
|
||||||
path: string,
|
|
||||||
item: HomeCardContent,
|
|
||||||
iconLabel: string,
|
|
||||||
iconPlaceholder: string,
|
|
||||||
): DesignerField[] {
|
|
||||||
return [
|
|
||||||
iconField(`${path}.icon`, iconLabel, item.icon, { placeholder: iconPlaceholder }),
|
|
||||||
textField(`${path}.title`, item.titleKey, item.title),
|
|
||||||
textAreaField(`${path}.description`, item.descriptionKey, item.description),
|
|
||||||
styleField(`${path}.styleClass`, `${path}.styleClass`, item.styleClass),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
|
||||||
const { openDemo } = useOptionalDemo()
|
|
||||||
const { translate } = useLocalization()
|
|
||||||
const [home, setHome] = useState<HomeDto>()
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [currentSlide, setCurrentSlide] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchHome = async () => {
|
|
||||||
setLoading(true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await getHome()
|
|
||||||
setHome(result.data)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Home alinirken hata olustu:', error)
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchHome()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const initialContent = useMemo(
|
|
||||||
() => (loading ? null : buildHomeContent(home, translate)),
|
|
||||||
[home, loading, translate],
|
|
||||||
)
|
|
||||||
|
|
||||||
const designer = usePageDesigner<HomeContent>({
|
|
||||||
pageKey: 'Home',
|
|
||||||
permission: PUBLIC_PAGE_DESIGN.HOME,
|
|
||||||
initialContent,
|
|
||||||
onSave: (content, cultureName) => saveHomePage(toSaveInput(content, cultureName)),
|
|
||||||
})
|
|
||||||
|
|
||||||
const { content, isDesignMode, isPanelVisible, selectedBlockId, selectBlock } = designer
|
|
||||||
const slides = useMemo(() => content?.slides ?? [], [content?.slides])
|
|
||||||
const slideCount = slides.length
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (slideCount > 0 && currentSlide >= slideCount) {
|
|
||||||
setCurrentSlide(0)
|
|
||||||
}
|
|
||||||
}, [currentSlide, slideCount])
|
|
||||||
|
|
||||||
// Tasarim modunda otomatik gecis kapalidir; aksi halde duzenlenen slayt kayiyor.
|
|
||||||
useEffect(() => {
|
|
||||||
if (slideCount < 2 || isDesignMode) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const timer = setInterval(
|
|
||||||
() => setCurrentSlide((previous) => (previous + 1) % slideCount),
|
|
||||||
SLIDE_INTERVAL_MS,
|
|
||||||
)
|
|
||||||
|
|
||||||
return () => clearInterval(timer)
|
|
||||||
}, [isDesignMode, slideCount])
|
|
||||||
|
|
||||||
const goToSlide = (offset: number) =>
|
|
||||||
setCurrentSlide(
|
|
||||||
(previous) => (previous + offset + Math.max(slideCount, 1)) % Math.max(slideCount, 1),
|
|
||||||
)
|
|
||||||
|
|
||||||
const selection: DesignerSelection | null = useMemo(() => {
|
|
||||||
if (!content || !selectedBlockId) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const iconLabel = translateLabel(translate, 'App.PublicDesigner.IkonAnahtari')
|
|
||||||
|
|
||||||
if (selectedBlockId === 'hero') {
|
|
||||||
return {
|
|
||||||
id: 'hero',
|
|
||||||
title: 'Public.home.hero.*',
|
|
||||||
description: translateLabel(translate, 'Public.designer.heroHint'),
|
|
||||||
fields: [
|
|
||||||
textField('heroPrimaryCtaLabel', content.heroPrimaryCtaKey, content.heroPrimaryCtaLabel),
|
|
||||||
textField(
|
|
||||||
'heroSecondaryCtaLabel',
|
|
||||||
content.heroSecondaryCtaKey,
|
|
||||||
content.heroSecondaryCtaLabel,
|
|
||||||
),
|
|
||||||
imageField(
|
|
||||||
'heroBackgroundImage',
|
|
||||||
content.heroBackgroundImageKey,
|
|
||||||
content.heroBackgroundImage,
|
|
||||||
{
|
|
||||||
group: 'media',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'heroPrimaryCtaStyle',
|
|
||||||
content.heroPrimaryCtaStyleKey,
|
|
||||||
content.heroPrimaryCtaStyle,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'heroSecondaryCtaStyle',
|
|
||||||
content.heroSecondaryCtaStyleKey,
|
|
||||||
content.heroSecondaryCtaStyle,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId.startsWith('slide-')) {
|
|
||||||
const index = Number(selectedBlockId.replace('slide-', ''))
|
|
||||||
const slide = content.slides[index]
|
|
||||||
|
|
||||||
if (!slide) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: slide.titleKey || `Slide ${index + 1}`,
|
|
||||||
description: translateLabel(translate, 'Public.designer.slideHint'),
|
|
||||||
fields: [
|
|
||||||
textField(`slides.${index}.title`, slide.titleKey, slide.title),
|
|
||||||
textAreaField(`slides.${index}.subtitle`, slide.subtitleKey, slide.subtitle),
|
|
||||||
styleField(
|
|
||||||
`slides.${index}.styleClass`,
|
|
||||||
`Slide${index + 1}.StyleClass`,
|
|
||||||
slide.styleClass,
|
|
||||||
),
|
|
||||||
...slide.services.flatMap((service, serviceIndex) => {
|
|
||||||
const path = `slides.${index}.services.${serviceIndex}`
|
|
||||||
|
|
||||||
return [
|
|
||||||
iconField(
|
|
||||||
`${path}.icon`,
|
|
||||||
`Slide${index + 1}.Service${serviceIndex + 1}.Icon`,
|
|
||||||
service.icon,
|
|
||||||
{
|
|
||||||
placeholder: translate('::App.PublicDesigner.IconPlaceholder', { icon: 'FaUsers' }),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
textField(`${path}.title`, service.titleKey, service.title),
|
|
||||||
textAreaField(`${path}.description`, service.descriptionKey, service.description),
|
|
||||||
styleField(
|
|
||||||
`${path}.styleClass`,
|
|
||||||
`Slide${index + 1}.Service${serviceIndex + 1}.StyleClass`,
|
|
||||||
service.styleClass,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'features-heading') {
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: content.featuresTitleKey,
|
|
||||||
description: translateLabel(translate, 'Public.designer.headingHint'),
|
|
||||||
fields: [
|
|
||||||
textField('featuresTitle', content.featuresTitleKey, content.featuresTitle),
|
|
||||||
textAreaField('featuresSubtitle', content.featuresSubtitleKey, content.featuresSubtitle),
|
|
||||||
styleField(
|
|
||||||
'featuresTitleStyle',
|
|
||||||
content.featuresTitleStyleKey,
|
|
||||||
content.featuresTitleStyle,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'featuresSubtitleStyle',
|
|
||||||
content.featuresSubtitleStyleKey,
|
|
||||||
content.featuresSubtitleStyle,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'solutions-heading') {
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: content.solutionsTitleKey,
|
|
||||||
description: translateLabel(translate, 'Public.designer.headingHint'),
|
|
||||||
fields: [
|
|
||||||
textField('solutionsTitle', content.solutionsTitleKey, content.solutionsTitle),
|
|
||||||
textAreaField(
|
|
||||||
'solutionsSubtitle',
|
|
||||||
content.solutionsSubtitleKey,
|
|
||||||
content.solutionsSubtitle,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'solutionsTitleStyle',
|
|
||||||
content.solutionsTitleStyleKey,
|
|
||||||
content.solutionsTitleStyle,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'solutionsSubtitleStyle',
|
|
||||||
content.solutionsSubtitleStyleKey,
|
|
||||||
content.solutionsSubtitleStyle,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId.startsWith('feature-')) {
|
|
||||||
const index = Number(selectedBlockId.replace('feature-', ''))
|
|
||||||
const item = content.features[index]
|
|
||||||
|
|
||||||
return item
|
|
||||||
? {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: item.titleKey || `Feature ${index + 1}`,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc1'),
|
|
||||||
fields: cardFields(
|
|
||||||
`features.${index}`,
|
|
||||||
item,
|
|
||||||
iconLabel,
|
|
||||||
translate('::App.PublicDesigner.IconPlaceholder', { icon: 'FaChartBar' }),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId.startsWith('solution-')) {
|
|
||||||
const index = Number(selectedBlockId.replace('solution-', ''))
|
|
||||||
const item = content.solutions[index]
|
|
||||||
|
|
||||||
return item
|
|
||||||
? {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: item.titleKey || `Solution ${index + 1}`,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc1'),
|
|
||||||
fields: [
|
|
||||||
...cardFields(
|
|
||||||
`solutions.${index}`,
|
|
||||||
item,
|
|
||||||
iconLabel,
|
|
||||||
translate('::App.PublicDesigner.IconPlaceholder', { icon: 'FaChartBar' }),
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
`solutions.${index}.colorClass`,
|
|
||||||
`Solution${index + 1}.ColorClass`,
|
|
||||||
item.colorClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'cta') {
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: content.ctaTitleKey,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc2'),
|
|
||||||
fields: [
|
|
||||||
textField('ctaTitle', content.ctaTitleKey, content.ctaTitle),
|
|
||||||
textAreaField('ctaSubtitle', content.ctaSubtitleKey, content.ctaSubtitle),
|
|
||||||
textField('ctaButtonLabel', content.ctaButtonLabelKey, content.ctaButtonLabel),
|
|
||||||
styleField('ctaTitleStyle', content.ctaTitleStyleKey, content.ctaTitleStyle),
|
|
||||||
styleField('ctaSubtitleStyle', content.ctaSubtitleStyleKey, content.ctaSubtitleStyle),
|
|
||||||
styleField('ctaButtonStyle', content.ctaButtonStyleKey, content.ctaButtonStyle),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}, [content, selectedBlockId, translate])
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <PublicPageLoader />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={designerPageClass(isDesignMode, isPanelVisible)}>
|
|
||||||
<PageTitle title={translate('::App.Home')} />
|
|
||||||
|
|
||||||
{/* Hero carousel */}
|
|
||||||
<div className="relative">
|
|
||||||
<SelectableBlock
|
|
||||||
id="hero"
|
|
||||||
label="Hero"
|
|
||||||
isActive={selectedBlockId === 'hero'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className="relative min-h-screen overflow-hidden bg-blue-900 p-20 text-white dark:bg-gray-950">
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-900 via-indigo-900 to-purple-900 dark:from-gray-950 dark:via-gray-950 dark:to-gray-950" />
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 opacity-20 dark:opacity-35"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url("${content?.heroBackgroundImage || HOME_HERO_DEFAULT_IMAGE}")`,
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'center',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-blue-950/25 dark:bg-gray-950/45" />
|
|
||||||
|
|
||||||
<div className="container relative mx-auto flex h-screen items-center px-4 pb-16 pt-32">
|
|
||||||
{slides.map((slide, index) => (
|
|
||||||
<div
|
|
||||||
key={slide.titleKey || index}
|
|
||||||
aria-hidden={index !== currentSlide}
|
|
||||||
className={`absolute inset-0 transition-all duration-700 ease-in-out ${
|
|
||||||
index === currentSlide
|
|
||||||
? 'opacity-100 translate-x-0'
|
|
||||||
: index < currentSlide
|
|
||||||
? 'pointer-events-none opacity-0 -translate-x-full'
|
|
||||||
: 'pointer-events-none opacity-0 translate-x-full'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<SelectableBlock
|
|
||||||
id={`slide-${index}`}
|
|
||||||
label={`Slide ${index + 1}`}
|
|
||||||
isActive={selectedBlockId === `slide-${index}`}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
className="h-full"
|
|
||||||
onSelect={(id) => {
|
|
||||||
setCurrentSlide(index)
|
|
||||||
selectBlock(id)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className={`container mx-auto px-4 pt-16 ${slide.styleClass}`}>
|
|
||||||
<div className="mx-auto max-w-4xl text-center">
|
|
||||||
<h1 className="animate-fade-in mb-6 text-3xl font-bold text-white md:text-6xl">
|
|
||||||
{slide.title}
|
|
||||||
</h1>
|
|
||||||
<p className="animate-fade-in-delay mb-12 text-xl text-gray-300 md:text-2xl">
|
|
||||||
{slide.subtitle}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="mb-16 flex flex-col justify-center gap-6 md:flex-row">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={
|
|
||||||
content?.heroPrimaryCtaStyle || HERO_PRIMARY_CTA_STYLE_DEFAULT
|
|
||||||
}
|
|
||||||
onClick={openDemo}
|
|
||||||
>
|
|
||||||
{content?.heroPrimaryCtaLabel}
|
|
||||||
<FaArrowRight className="ml-2" size={20} />
|
|
||||||
</button>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.products}
|
|
||||||
className={
|
|
||||||
content?.heroSecondaryCtaStyle || HERO_SECONDARY_CTA_STYLE_DEFAULT
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{content?.heroSecondaryCtaLabel}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mx-auto grid max-w-4xl grid-cols-1 gap-8 md:grid-cols-3">
|
|
||||||
{slide.services.map((service, serviceIndex) => (
|
|
||||||
<div
|
|
||||||
key={service.titleKey || serviceIndex}
|
|
||||||
className={service.styleClass || SLIDE_SERVICE_STYLE_DEFAULT}
|
|
||||||
>
|
|
||||||
<PageIcon
|
|
||||||
name={service.icon}
|
|
||||||
className="mx-auto mb-4 text-blue-300"
|
|
||||||
size={40}
|
|
||||||
/>
|
|
||||||
<h3 className="mb-3 text-xl font-semibold text-white">
|
|
||||||
{service.title}
|
|
||||||
</h3>
|
|
||||||
<p className="text-gray-300">{service.description}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{slideCount > 1 && (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
variant="plain"
|
|
||||||
shape="circle"
|
|
||||||
icon={<FaChevronLeft size={24} />}
|
|
||||||
className="absolute left-4 top-1/2 z-10 -translate-y-1/2 !h-14 !w-14 !px-0 bg-white/10 text-white backdrop-blur-sm transition-all hover:!bg-white/20"
|
|
||||||
aria-label="Previous slide"
|
|
||||||
onClick={() => goToSlide(-1)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="plain"
|
|
||||||
shape="circle"
|
|
||||||
icon={<FaChevronRight size={24} />}
|
|
||||||
className="absolute right-4 top-1/2 z-10 -translate-y-1/2 !h-14 !w-14 !px-0 bg-white/10 text-white backdrop-blur-sm transition-all hover:!bg-white/20"
|
|
||||||
aria-label="Next slide"
|
|
||||||
onClick={() => goToSlide(1)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="absolute bottom-8 left-1/2 z-10 flex -translate-x-1/2 gap-3">
|
|
||||||
{slides.map((slide, index) => (
|
|
||||||
<Button
|
|
||||||
key={`indicator-${slide.titleKey || index}`}
|
|
||||||
variant="plain"
|
|
||||||
shape="circle"
|
|
||||||
className={`!h-3 !min-h-3 !px-0 rounded-full transition-all ${
|
|
||||||
index === currentSlide
|
|
||||||
? '!w-8 bg-white'
|
|
||||||
: '!w-3 bg-white/50 hover:bg-white/70'
|
|
||||||
}`}
|
|
||||||
aria-label={`Go to slide ${index + 1}`}
|
|
||||||
onClick={() => setCurrentSlide(index)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
|
|
||||||
{/* Tasarim modunda slayt secici; secilebilir blogun disinda durur ki tiklamalar yutulmasin. */}
|
|
||||||
{isDesignMode && slideCount > 0 && (
|
|
||||||
<div className="absolute bottom-24 left-1/2 z-30 flex -translate-x-1/2 gap-2 rounded-full bg-black/40 px-3 py-2 backdrop-blur">
|
|
||||||
{slides.map((slide, index) => (
|
|
||||||
<Button
|
|
||||||
key={`editor-slide-${slide.titleKey || index}`}
|
|
||||||
type="button"
|
|
||||||
variant="plain"
|
|
||||||
className={`!h-auto !rounded-full !px-3 !py-1 text-xs font-semibold text-white transition ${
|
|
||||||
selectedBlockId === `slide-${index}`
|
|
||||||
? 'bg-sky-500'
|
|
||||||
: 'bg-white/20 hover:bg-white/30'
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
|
||||||
setCurrentSlide(index)
|
|
||||||
selectBlock(`slide-${index}`)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Slide {index + 1}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Features */}
|
|
||||||
<section className="bg-white py-20 dark:bg-gray-950">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<SelectableBlock
|
|
||||||
id="features-heading"
|
|
||||||
label="Features"
|
|
||||||
isActive={selectedBlockId === 'features-heading'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className="mb-16 text-center">
|
|
||||||
<h2 className={content?.featuresTitleStyle || FEATURES_TITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.featuresTitle}
|
|
||||||
</h2>
|
|
||||||
<p className={content?.featuresSubtitleStyle || FEATURES_SUBTITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.featuresSubtitle}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-4">
|
|
||||||
{content?.features.map((feature, index) => (
|
|
||||||
<SelectableBlock
|
|
||||||
key={feature.titleKey || index}
|
|
||||||
id={`feature-${index}`}
|
|
||||||
label={`Feature ${index + 1}`}
|
|
||||||
isActive={selectedBlockId === `feature-${index}`}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={feature.styleClass || FEATURE_CARD_STYLE_DEFAULT}>
|
|
||||||
<PageIcon name={feature.icon} className="mb-6 h-12 w-12 text-blue-500" />
|
|
||||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
|
||||||
{feature.title}
|
|
||||||
</h2>
|
|
||||||
<p className="text-gray-600 dark:text-gray-300">{feature.description}</p>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Solutions */}
|
|
||||||
<section className="bg-gray-50 py-20 dark:bg-gray-900">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<SelectableBlock
|
|
||||||
id="solutions-heading"
|
|
||||||
label="Solutions"
|
|
||||||
isActive={selectedBlockId === 'solutions-heading'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className="mb-16 text-center">
|
|
||||||
<h2 className={content?.solutionsTitleStyle || SOLUTIONS_TITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.solutionsTitle}
|
|
||||||
</h2>
|
|
||||||
<p className={content?.solutionsSubtitleStyle || SOLUTIONS_SUBTITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.solutionsSubtitle}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-4">
|
|
||||||
{content?.solutions.map((solution, index) => (
|
|
||||||
<SelectableBlock
|
|
||||||
key={solution.titleKey || index}
|
|
||||||
id={`solution-${index}`}
|
|
||||||
label={`Solution ${index + 1}`}
|
|
||||||
isActive={selectedBlockId === `solution-${index}`}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`${solution.colorClass} ${solution.styleClass || SOLUTION_CARD_STYLE_DEFAULT}`}
|
|
||||||
>
|
|
||||||
<PageIcon name={solution.icon} className="mb-6 h-16 w-16 text-white" />
|
|
||||||
<h3 className="mb-4 text-2xl font-semibold text-white">{solution.title}</h3>
|
|
||||||
<p className="text-white/90">{solution.description}</p>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* CTA */}
|
|
||||||
<SelectableBlock
|
|
||||||
id="cta"
|
|
||||||
label="CTA"
|
|
||||||
isActive={selectedBlockId === 'cta'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<section className="bg-blue-900 py-16 dark:bg-gray-800">
|
|
||||||
<div className="container mx-auto px-4 text-center">
|
|
||||||
<h2 className={content?.ctaTitleStyle || CTA_TITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.ctaTitle}
|
|
||||||
</h2>
|
|
||||||
<p className={content?.ctaSubtitleStyle || CTA_SUBTITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.ctaSubtitle}
|
|
||||||
</p>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.contact}
|
|
||||||
className={content?.ctaButtonStyle || CTA_BUTTON_STYLE_DEFAULT}
|
|
||||||
>
|
|
||||||
{content?.ctaButtonLabel}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</SelectableBlock>
|
|
||||||
|
|
||||||
<DesignerLayer pageTitle="Home" selection={selection} designer={designer} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Home
|
|
||||||
|
|
@ -1,740 +0,0 @@
|
||||||
import React, { useEffect, useMemo, useState } from 'react'
|
|
||||||
import { FaCheckCircle } from 'react-icons/fa'
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import PageTitle from '@/components/shared/PageTitle'
|
|
||||||
import { ServiceDto } from '@/proxy/services/models'
|
|
||||||
import { getServices, saveServicesPage } from '@/services/service.service'
|
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
|
||||||
import { PUBLIC_PAGE_DESIGN } from '@/constants/permission.constant'
|
|
||||||
import {
|
|
||||||
DesignerLayer,
|
|
||||||
DesignerField,
|
|
||||||
DesignerSelection,
|
|
||||||
SelectableBlock,
|
|
||||||
TranslateFn,
|
|
||||||
iconField,
|
|
||||||
imageField,
|
|
||||||
isLikelyLocalizationKey,
|
|
||||||
resolveLocalizedValue,
|
|
||||||
styleField,
|
|
||||||
textAreaField,
|
|
||||||
textField,
|
|
||||||
translateLabel,
|
|
||||||
usePageDesigner,
|
|
||||||
} from './designer'
|
|
||||||
import { designerPageClass, getIconColor } from './pageStyles'
|
|
||||||
import { PageIcon, PublicPageLoader } from './shared'
|
|
||||||
|
|
||||||
interface ServiceFeatureContent {
|
|
||||||
key: string
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ServiceCardContent {
|
|
||||||
icon: string
|
|
||||||
title: string
|
|
||||||
titleKey: string
|
|
||||||
description: string
|
|
||||||
descriptionKey: string
|
|
||||||
cardStyleClassKey: string
|
|
||||||
cardStyleClass: string
|
|
||||||
titleStyleClassKey: string
|
|
||||||
titleStyleClass: string
|
|
||||||
descriptionStyleClassKey: string
|
|
||||||
descriptionStyleClass: string
|
|
||||||
featureStyleClassKey: string
|
|
||||||
featureStyleClass: string
|
|
||||||
features: ServiceFeatureContent[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SupportCardContent {
|
|
||||||
icon: string
|
|
||||||
title: string
|
|
||||||
titleKey: string
|
|
||||||
cardStyleClassKey: string
|
|
||||||
cardStyleClass: string
|
|
||||||
titleStyleClassKey: string
|
|
||||||
titleStyleClass: string
|
|
||||||
features: ServiceFeatureContent[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ServicesContent {
|
|
||||||
heroTitle: string
|
|
||||||
heroTitleKey: string
|
|
||||||
heroSectionStyleClassKey: string
|
|
||||||
heroSectionStyleClass: string
|
|
||||||
heroTitleStyleClassKey: string
|
|
||||||
heroTitleStyleClass: string
|
|
||||||
heroSubtitle: string
|
|
||||||
heroSubtitleKey: string
|
|
||||||
heroSubtitleStyleClassKey: string
|
|
||||||
heroSubtitleStyleClass: string
|
|
||||||
heroImage: string
|
|
||||||
heroImageKey: string
|
|
||||||
serviceItems: ServiceCardContent[]
|
|
||||||
supportTitle: string
|
|
||||||
supportTitleKey: string
|
|
||||||
supportTitleStyleClassKey: string
|
|
||||||
supportTitleStyleClass: string
|
|
||||||
supportButtonStyleClassKey: string
|
|
||||||
supportButtonStyleClass: string
|
|
||||||
supportItems: SupportCardContent[]
|
|
||||||
supportButtonLabel: string
|
|
||||||
supportButtonLabelKey: string
|
|
||||||
ctaTitle: string
|
|
||||||
ctaTitleKey: string
|
|
||||||
ctaSectionStyleClassKey: string
|
|
||||||
ctaSectionStyleClass: string
|
|
||||||
ctaTitleStyleClassKey: string
|
|
||||||
ctaTitleStyleClass: string
|
|
||||||
ctaDescription: string
|
|
||||||
ctaDescriptionKey: string
|
|
||||||
ctaDescriptionStyleClassKey: string
|
|
||||||
ctaDescriptionStyleClass: string
|
|
||||||
ctaButtonLabel: string
|
|
||||||
ctaButtonLabelKey: string
|
|
||||||
ctaButtonStyleClassKey: string
|
|
||||||
ctaButtonStyleClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const SERVICES_HERO_IMAGE =
|
|
||||||
'https://images.pexels.com/photos/3183173/pexels-photo-3183173.jpeg?auto=compress&cs=tinysrgb&w=1920'
|
|
||||||
const SERVICES_HERO_TITLE_KEY = 'App.PublicServices.ServicesTitle'
|
|
||||||
const SERVICES_HERO_SUBTITLE_KEY = 'App.PublicServices.Subtitle'
|
|
||||||
const SERVICES_HERO_IMAGE_KEY = 'Public.services.heroImage'
|
|
||||||
const SERVICES_SUPPORT_TITLE_KEY = 'App.ServicesSupport.SupportTitle'
|
|
||||||
const SERVICES_SUPPORT_BUTTON_KEY = 'App.ServicesSupport.ContactButton'
|
|
||||||
const SERVICES_CTA_TITLE_KEY = 'App.ServicesCta.CtaTitle'
|
|
||||||
const SERVICES_CTA_DESCRIPTION_KEY = 'App.ServicesCta.CtaDescription'
|
|
||||||
// Destek karti ve CTA butonu ayni anahtari paylastigi icin biri duzenlendiginde digeri de
|
|
||||||
// degisiyordu; CTA butonu artik kendi anahtarini kullanir, ceviri yoksa destek metnine duser.
|
|
||||||
const SERVICES_CTA_BUTTON_KEY = 'Public.services.cta.contactButton'
|
|
||||||
const SERVICES_HERO_SECTION_STYLE_KEY = 'Public.services.hero.sectionStyleClass'
|
|
||||||
const SERVICES_HERO_TITLE_STYLE_KEY = 'Public.services.hero.titleStyleClass'
|
|
||||||
const SERVICES_HERO_SUBTITLE_STYLE_KEY = 'Public.services.hero.subtitleStyleClass'
|
|
||||||
const SERVICES_SUPPORT_TITLE_STYLE_KEY = 'Public.services.support.titleStyleClass'
|
|
||||||
const SERVICES_SUPPORT_BUTTON_STYLE_KEY = 'Public.services.support.buttonStyleClass'
|
|
||||||
const SERVICES_CTA_SECTION_STYLE_KEY = 'Public.services.cta.sectionStyleClass'
|
|
||||||
const SERVICES_CTA_TITLE_STYLE_KEY = 'Public.services.cta.titleStyleClass'
|
|
||||||
const SERVICES_CTA_DESCRIPTION_STYLE_KEY = 'Public.services.cta.descriptionStyleClass'
|
|
||||||
const SERVICES_CTA_BUTTON_STYLE_KEY = 'Public.services.cta.buttonStyleClass'
|
|
||||||
|
|
||||||
const HERO_SECTION_STYLE_DEFAULT = 'relative bg-blue-900 text-white py-12 dark:bg-gray-950'
|
|
||||||
const HERO_TITLE_STYLE_DEFAULT = 'text-5xl font-bold ml-4 mt-3 mb-2 text-white'
|
|
||||||
const HERO_SUBTITLE_STYLE_DEFAULT = 'text-xl max-w-3xl ml-4'
|
|
||||||
const SERVICE_CARD_STYLE_DEFAULT =
|
|
||||||
'bg-white dark:bg-gray-900 rounded-xl shadow-lg p-8 hover:shadow-xl transition-shadow dark:shadow-gray-950/40'
|
|
||||||
const SERVICE_TITLE_STYLE_DEFAULT = 'text-2xl font-bold text-gray-900 dark:text-gray-100 mb-4'
|
|
||||||
const SERVICE_DESCRIPTION_STYLE_DEFAULT = 'text-gray-600 dark:text-gray-300 mb-6'
|
|
||||||
const SERVICE_FEATURE_STYLE_DEFAULT = 'flex items-center text-gray-700 dark:text-gray-300'
|
|
||||||
const SUPPORT_CARD_STYLE_DEFAULT =
|
|
||||||
'bg-white dark:bg-gray-900 rounded-xl shadow-lg p-8 border border-gray-200 dark:border-gray-700 dark:shadow-gray-950/40'
|
|
||||||
const SUPPORT_TITLE_STYLE_DEFAULT = 'text-xl font-bold mb-4 text-gray-900 dark:text-gray-100'
|
|
||||||
const SUPPORT_HEADING_STYLE_DEFAULT =
|
|
||||||
'text-3xl font-bold text-center mb-10 text-gray-900 dark:text-gray-100'
|
|
||||||
const SUPPORT_BUTTON_STYLE_DEFAULT =
|
|
||||||
'block text-center bg-blue-600 text-white font-medium px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white'
|
|
||||||
const CTA_SECTION_STYLE_DEFAULT = 'bg-blue-900 text-white py-16 dark:bg-gray-800'
|
|
||||||
const CTA_TITLE_STYLE_DEFAULT = 'text-3xl font-bold mb-6 text-white'
|
|
||||||
const CTA_DESCRIPTION_STYLE_DEFAULT = 'text-xl mb-8 max-w-2xl mx-auto'
|
|
||||||
const CTA_BUTTON_STYLE_DEFAULT =
|
|
||||||
'inline-block bg-white text-blue-900 px-8 py-3 rounded-lg font-semibold hover:bg-blue-50 transition-colors'
|
|
||||||
|
|
||||||
function buildFeatures(features: string[] | undefined, prefix: string, translate: TranslateFn) {
|
|
||||||
return (features ?? []).map((feature, index) => ({
|
|
||||||
key: (isLikelyLocalizationKey(feature) ? feature : '') || `${prefix}.feature.${index + 1}`,
|
|
||||||
value: resolveLocalizedValue(translate, feature, feature),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildServicesContent(services: ServiceDto[], translate: TranslateFn): ServicesContent {
|
|
||||||
const supportButtonLabel = resolveLocalizedValue(translate, SERVICES_SUPPORT_BUTTON_KEY)
|
|
||||||
|
|
||||||
return {
|
|
||||||
heroTitle: resolveLocalizedValue(translate, SERVICES_HERO_TITLE_KEY, 'Services'),
|
|
||||||
heroTitleKey: SERVICES_HERO_TITLE_KEY,
|
|
||||||
heroSectionStyleClassKey: SERVICES_HERO_SECTION_STYLE_KEY,
|
|
||||||
heroSectionStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
SERVICES_HERO_SECTION_STYLE_KEY,
|
|
||||||
HERO_SECTION_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
heroTitleStyleClassKey: SERVICES_HERO_TITLE_STYLE_KEY,
|
|
||||||
heroTitleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
SERVICES_HERO_TITLE_STYLE_KEY,
|
|
||||||
HERO_TITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
heroSubtitle: resolveLocalizedValue(translate, SERVICES_HERO_SUBTITLE_KEY),
|
|
||||||
heroSubtitleKey: SERVICES_HERO_SUBTITLE_KEY,
|
|
||||||
heroSubtitleStyleClassKey: SERVICES_HERO_SUBTITLE_STYLE_KEY,
|
|
||||||
heroSubtitleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
SERVICES_HERO_SUBTITLE_STYLE_KEY,
|
|
||||||
HERO_SUBTITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
heroImage: resolveLocalizedValue(translate, SERVICES_HERO_IMAGE_KEY, SERVICES_HERO_IMAGE),
|
|
||||||
heroImageKey: SERVICES_HERO_IMAGE_KEY,
|
|
||||||
serviceItems: services
|
|
||||||
.filter((item) => item.type === 'service')
|
|
||||||
.map((item, index) => {
|
|
||||||
const prefix = `Public.services.dynamic.service.${index + 1}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
icon: item.icon || '',
|
|
||||||
title: resolveLocalizedValue(translate, item.title, item.title),
|
|
||||||
titleKey: (isLikelyLocalizationKey(item.title) ? item.title : '') || `${prefix}.title`,
|
|
||||||
description: resolveLocalizedValue(translate, item.description, item.description || ''),
|
|
||||||
descriptionKey:
|
|
||||||
(item.description && isLikelyLocalizationKey(item.description)
|
|
||||||
? item.description
|
|
||||||
: '') || `${prefix}.description`,
|
|
||||||
cardStyleClassKey: `${prefix}.cardStyleClass`,
|
|
||||||
cardStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.cardStyleClass`,
|
|
||||||
SERVICE_CARD_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
titleStyleClassKey: `${prefix}.titleStyleClass`,
|
|
||||||
titleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.titleStyleClass`,
|
|
||||||
SERVICE_TITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
descriptionStyleClassKey: `${prefix}.descriptionStyleClass`,
|
|
||||||
descriptionStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.descriptionStyleClass`,
|
|
||||||
SERVICE_DESCRIPTION_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
featureStyleClassKey: `${prefix}.featureStyleClass`,
|
|
||||||
featureStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.featureStyleClass`,
|
|
||||||
SERVICE_FEATURE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
features: buildFeatures(item.features, prefix, translate),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
supportTitle: resolveLocalizedValue(translate, SERVICES_SUPPORT_TITLE_KEY),
|
|
||||||
supportTitleKey: SERVICES_SUPPORT_TITLE_KEY,
|
|
||||||
supportTitleStyleClassKey: SERVICES_SUPPORT_TITLE_STYLE_KEY,
|
|
||||||
supportTitleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
SERVICES_SUPPORT_TITLE_STYLE_KEY,
|
|
||||||
SUPPORT_HEADING_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
supportButtonStyleClassKey: SERVICES_SUPPORT_BUTTON_STYLE_KEY,
|
|
||||||
supportButtonStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
SERVICES_SUPPORT_BUTTON_STYLE_KEY,
|
|
||||||
SUPPORT_BUTTON_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
supportItems: services
|
|
||||||
.filter((item) => item.type === 'support')
|
|
||||||
.map((item, index) => {
|
|
||||||
const prefix = `Public.services.dynamic.support.${index + 1}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
icon: item.icon || '',
|
|
||||||
title: resolveLocalizedValue(translate, item.title, item.title),
|
|
||||||
titleKey: (isLikelyLocalizationKey(item.title) ? item.title : '') || `${prefix}.title`,
|
|
||||||
cardStyleClassKey: `${prefix}.cardStyleClass`,
|
|
||||||
cardStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.cardStyleClass`,
|
|
||||||
SUPPORT_CARD_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
titleStyleClassKey: `${prefix}.titleStyleClass`,
|
|
||||||
titleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
`${prefix}.titleStyleClass`,
|
|
||||||
SUPPORT_TITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
features: buildFeatures(item.features, prefix, translate),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
supportButtonLabel,
|
|
||||||
supportButtonLabelKey: SERVICES_SUPPORT_BUTTON_KEY,
|
|
||||||
ctaTitle: resolveLocalizedValue(translate, SERVICES_CTA_TITLE_KEY),
|
|
||||||
ctaTitleKey: SERVICES_CTA_TITLE_KEY,
|
|
||||||
ctaSectionStyleClassKey: SERVICES_CTA_SECTION_STYLE_KEY,
|
|
||||||
ctaSectionStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
SERVICES_CTA_SECTION_STYLE_KEY,
|
|
||||||
CTA_SECTION_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
ctaTitleStyleClassKey: SERVICES_CTA_TITLE_STYLE_KEY,
|
|
||||||
ctaTitleStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
SERVICES_CTA_TITLE_STYLE_KEY,
|
|
||||||
CTA_TITLE_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
ctaDescription: resolveLocalizedValue(translate, SERVICES_CTA_DESCRIPTION_KEY),
|
|
||||||
ctaDescriptionKey: SERVICES_CTA_DESCRIPTION_KEY,
|
|
||||||
ctaDescriptionStyleClassKey: SERVICES_CTA_DESCRIPTION_STYLE_KEY,
|
|
||||||
ctaDescriptionStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
SERVICES_CTA_DESCRIPTION_STYLE_KEY,
|
|
||||||
CTA_DESCRIPTION_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
ctaButtonLabel: resolveLocalizedValue(translate, SERVICES_CTA_BUTTON_KEY, supportButtonLabel),
|
|
||||||
ctaButtonLabelKey: SERVICES_CTA_BUTTON_KEY,
|
|
||||||
ctaButtonStyleClassKey: SERVICES_CTA_BUTTON_STYLE_KEY,
|
|
||||||
ctaButtonStyleClass: resolveLocalizedValue(
|
|
||||||
translate,
|
|
||||||
SERVICES_CTA_BUTTON_STYLE_KEY,
|
|
||||||
CTA_BUTTON_STYLE_DEFAULT,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toSaveInput(content: ServicesContent, cultureName: string) {
|
|
||||||
return {
|
|
||||||
cultureName,
|
|
||||||
heroTitleKey: content.heroTitleKey,
|
|
||||||
heroTitleValue: content.heroTitle,
|
|
||||||
heroSubtitleKey: content.heroSubtitleKey,
|
|
||||||
heroSubtitleValue: content.heroSubtitle,
|
|
||||||
heroImageKey: content.heroImageKey,
|
|
||||||
heroImageValue: content.heroImage,
|
|
||||||
supportTitleKey: content.supportTitleKey,
|
|
||||||
supportTitleValue: content.supportTitle,
|
|
||||||
supportButtonLabelKey: content.supportButtonLabelKey,
|
|
||||||
supportButtonLabelValue: content.supportButtonLabel,
|
|
||||||
ctaTitleKey: content.ctaTitleKey,
|
|
||||||
ctaTitleValue: content.ctaTitle,
|
|
||||||
ctaDescriptionKey: content.ctaDescriptionKey,
|
|
||||||
ctaDescriptionValue: content.ctaDescription,
|
|
||||||
ctaButtonLabelKey: content.ctaButtonLabelKey,
|
|
||||||
ctaButtonLabelValue: content.ctaButtonLabel,
|
|
||||||
serviceItems: content.serviceItems.map((item, index) => ({
|
|
||||||
icon: item.icon,
|
|
||||||
titleKey: item.titleKey || `Public.services.dynamic.service.${index + 1}.title`,
|
|
||||||
titleValue: item.title,
|
|
||||||
descriptionKey:
|
|
||||||
item.descriptionKey || `Public.services.dynamic.service.${index + 1}.description`,
|
|
||||||
descriptionValue: item.description,
|
|
||||||
type: 'service' as const,
|
|
||||||
features: item.features.map((feature, featureIndex) => ({
|
|
||||||
key:
|
|
||||||
feature.key || `Public.services.dynamic.service.${index + 1}.feature.${featureIndex + 1}`,
|
|
||||||
value: feature.value,
|
|
||||||
})),
|
|
||||||
})),
|
|
||||||
supportItems: content.supportItems.map((item, index) => ({
|
|
||||||
icon: item.icon,
|
|
||||||
titleKey: item.titleKey || `Public.services.dynamic.support.${index + 1}.title`,
|
|
||||||
titleValue: item.title,
|
|
||||||
type: 'support' as const,
|
|
||||||
features: item.features.map((feature, featureIndex) => ({
|
|
||||||
key:
|
|
||||||
feature.key || `Public.services.dynamic.support.${index + 1}.feature.${featureIndex + 1}`,
|
|
||||||
value: feature.value,
|
|
||||||
})),
|
|
||||||
})),
|
|
||||||
styleTexts: [
|
|
||||||
{ key: content.heroSectionStyleClassKey, value: content.heroSectionStyleClass },
|
|
||||||
{ key: content.heroTitleStyleClassKey, value: content.heroTitleStyleClass },
|
|
||||||
{ key: content.heroSubtitleStyleClassKey, value: content.heroSubtitleStyleClass },
|
|
||||||
{ key: content.supportTitleStyleClassKey, value: content.supportTitleStyleClass },
|
|
||||||
{ key: content.supportButtonStyleClassKey, value: content.supportButtonStyleClass },
|
|
||||||
{ key: content.ctaSectionStyleClassKey, value: content.ctaSectionStyleClass },
|
|
||||||
{ key: content.ctaTitleStyleClassKey, value: content.ctaTitleStyleClass },
|
|
||||||
{ key: content.ctaDescriptionStyleClassKey, value: content.ctaDescriptionStyleClass },
|
|
||||||
{ key: content.ctaButtonStyleClassKey, value: content.ctaButtonStyleClass },
|
|
||||||
...content.serviceItems.flatMap((item) => [
|
|
||||||
{ key: item.cardStyleClassKey, value: item.cardStyleClass },
|
|
||||||
{ key: item.titleStyleClassKey, value: item.titleStyleClass },
|
|
||||||
{ key: item.descriptionStyleClassKey, value: item.descriptionStyleClass },
|
|
||||||
{ key: item.featureStyleClassKey, value: item.featureStyleClass },
|
|
||||||
]),
|
|
||||||
...content.supportItems.flatMap((item) => [
|
|
||||||
{ key: item.cardStyleClassKey, value: item.cardStyleClass },
|
|
||||||
{ key: item.titleStyleClassKey, value: item.titleStyleClass },
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function featureFields(
|
|
||||||
pathPrefix: string,
|
|
||||||
features: ServiceFeatureContent[],
|
|
||||||
fallbackPrefix: string,
|
|
||||||
): DesignerField[] {
|
|
||||||
return features.map((feature, index) =>
|
|
||||||
textField(
|
|
||||||
`${pathPrefix}.features.${index}.value`,
|
|
||||||
feature.key || `${fallbackPrefix}.feature.${index + 1}`,
|
|
||||||
feature.value,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Services: React.FC = () => {
|
|
||||||
const { translate } = useLocalization()
|
|
||||||
const [services, setServices] = useState<ServiceDto[]>([])
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchServices = async () => {
|
|
||||||
setLoading(true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await getServices()
|
|
||||||
setServices(result?.data ?? [])
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Service listesi alinirken hata olustu:', error)
|
|
||||||
setServices([])
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchServices()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const initialContent = useMemo(
|
|
||||||
() => (loading ? null : buildServicesContent(services, translate)),
|
|
||||||
[loading, services, translate],
|
|
||||||
)
|
|
||||||
|
|
||||||
const designer = usePageDesigner<ServicesContent>({
|
|
||||||
pageKey: 'Services',
|
|
||||||
permission: PUBLIC_PAGE_DESIGN.SERVICES,
|
|
||||||
initialContent,
|
|
||||||
onSave: (content, cultureName) => saveServicesPage(toSaveInput(content, cultureName)),
|
|
||||||
})
|
|
||||||
|
|
||||||
const { content, isDesignMode, isPanelVisible, selectedBlockId, selectBlock } = designer
|
|
||||||
|
|
||||||
const selection: DesignerSelection | null = useMemo(() => {
|
|
||||||
if (!content || !selectedBlockId) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'hero') {
|
|
||||||
return {
|
|
||||||
id: 'hero',
|
|
||||||
title: content.heroTitleKey,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc4'),
|
|
||||||
fields: [
|
|
||||||
textField('heroTitle', content.heroTitleKey, content.heroTitle),
|
|
||||||
textAreaField('heroSubtitle', content.heroSubtitleKey, content.heroSubtitle),
|
|
||||||
imageField('heroImage', content.heroImageKey, content.heroImage, { group: 'media' }),
|
|
||||||
styleField(
|
|
||||||
'heroTitleStyleClass',
|
|
||||||
content.heroTitleStyleClassKey,
|
|
||||||
content.heroTitleStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'heroSubtitleStyleClass',
|
|
||||||
content.heroSubtitleStyleClassKey,
|
|
||||||
content.heroSubtitleStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'heroSectionStyleClass',
|
|
||||||
content.heroSectionStyleClassKey,
|
|
||||||
content.heroSectionStyleClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'support-heading') {
|
|
||||||
return {
|
|
||||||
id: 'support-heading',
|
|
||||||
title: content.supportTitleKey,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc3'),
|
|
||||||
fields: [
|
|
||||||
textField('supportTitle', content.supportTitleKey, content.supportTitle),
|
|
||||||
textField(
|
|
||||||
'supportButtonLabel',
|
|
||||||
content.supportButtonLabelKey,
|
|
||||||
content.supportButtonLabel,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'supportTitleStyleClass',
|
|
||||||
content.supportTitleStyleClassKey,
|
|
||||||
content.supportTitleStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'supportButtonStyleClass',
|
|
||||||
content.supportButtonStyleClassKey,
|
|
||||||
content.supportButtonStyleClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId === 'cta') {
|
|
||||||
return {
|
|
||||||
id: 'cta',
|
|
||||||
title: content.ctaTitleKey,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc2'),
|
|
||||||
fields: [
|
|
||||||
textField('ctaTitle', content.ctaTitleKey, content.ctaTitle),
|
|
||||||
textAreaField('ctaDescription', content.ctaDescriptionKey, content.ctaDescription),
|
|
||||||
textField('ctaButtonLabel', content.ctaButtonLabelKey, content.ctaButtonLabel),
|
|
||||||
styleField(
|
|
||||||
'ctaTitleStyleClass',
|
|
||||||
content.ctaTitleStyleClassKey,
|
|
||||||
content.ctaTitleStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'ctaDescriptionStyleClass',
|
|
||||||
content.ctaDescriptionStyleClassKey,
|
|
||||||
content.ctaDescriptionStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'ctaButtonStyleClass',
|
|
||||||
content.ctaButtonStyleClassKey,
|
|
||||||
content.ctaButtonStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
'ctaSectionStyleClass',
|
|
||||||
content.ctaSectionStyleClassKey,
|
|
||||||
content.ctaSectionStyleClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId.startsWith('service-')) {
|
|
||||||
const index = Number(selectedBlockId.replace('service-', ''))
|
|
||||||
const item = content.serviceItems[index]
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = `serviceItems.${index}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: item.titleKey,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc1'),
|
|
||||||
fields: [
|
|
||||||
iconField(
|
|
||||||
`${path}.icon`,
|
|
||||||
translateLabel(translate, 'App.PublicDesigner.IkonAnahtari'),
|
|
||||||
item.icon,
|
|
||||||
{ placeholder: translate('::App.PublicDesigner.IconPlaceholder', { icon: 'FaServer' }) },
|
|
||||||
),
|
|
||||||
textField(`${path}.title`, item.titleKey, item.title),
|
|
||||||
textAreaField(`${path}.description`, item.descriptionKey, item.description),
|
|
||||||
...featureFields(path, item.features, `Public.services.dynamic.service.${index + 1}`),
|
|
||||||
styleField(`${path}.cardStyleClass`, item.cardStyleClassKey, item.cardStyleClass),
|
|
||||||
styleField(`${path}.titleStyleClass`, item.titleStyleClassKey, item.titleStyleClass),
|
|
||||||
styleField(
|
|
||||||
`${path}.descriptionStyleClass`,
|
|
||||||
item.descriptionStyleClassKey,
|
|
||||||
item.descriptionStyleClass,
|
|
||||||
),
|
|
||||||
styleField(
|
|
||||||
`${path}.featureStyleClass`,
|
|
||||||
item.featureStyleClassKey,
|
|
||||||
item.featureStyleClass,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedBlockId.startsWith('support-')) {
|
|
||||||
const index = Number(selectedBlockId.replace('support-', ''))
|
|
||||||
const item = content.supportItems[index]
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = `supportItems.${index}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: selectedBlockId,
|
|
||||||
title: item.titleKey,
|
|
||||||
description: translateLabel(translate, 'App.PublicDesigner.DesignerDesc1'),
|
|
||||||
fields: [
|
|
||||||
iconField(
|
|
||||||
`${path}.icon`,
|
|
||||||
translateLabel(translate, 'App.PublicDesigner.IkonAnahtari'),
|
|
||||||
item.icon,
|
|
||||||
{ placeholder: translate('::App.PublicDesigner.IconPlaceholder', { icon: 'FaLifeRing' }) },
|
|
||||||
),
|
|
||||||
textField(`${path}.title`, item.titleKey, item.title),
|
|
||||||
...featureFields(path, item.features, `Public.services.dynamic.support.${index + 1}`),
|
|
||||||
styleField(`${path}.cardStyleClass`, item.cardStyleClassKey, item.cardStyleClass),
|
|
||||||
styleField(`${path}.titleStyleClass`, item.titleStyleClassKey, item.titleStyleClass),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}, [content, selectedBlockId, translate])
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <PublicPageLoader />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={designerPageClass(isDesignMode, isPanelVisible)}>
|
|
||||||
<PageTitle title={translate('::App.Services')} />
|
|
||||||
|
|
||||||
{/* Hero */}
|
|
||||||
<SelectableBlock
|
|
||||||
id="hero"
|
|
||||||
label="Hero"
|
|
||||||
isActive={selectedBlockId === 'hero'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={content?.heroSectionStyleClass || HERO_SECTION_STYLE_DEFAULT}>
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 opacity-20 dark:opacity-35"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url("${content?.heroImage || SERVICES_HERO_IMAGE}")`,
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'center',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-blue-950/25 dark:bg-gray-950/45" />
|
|
||||||
<div className="container relative mx-auto pt-20">
|
|
||||||
<h1 className={content?.heroTitleStyleClass || HERO_TITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.heroTitle}
|
|
||||||
</h1>
|
|
||||||
<p className={content?.heroSubtitleStyleClass || HERO_SUBTITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.heroSubtitle}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
|
|
||||||
{/* Services */}
|
|
||||||
<div className="py-6">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
|
|
||||||
{content?.serviceItems.map((service, index) => (
|
|
||||||
<SelectableBlock
|
|
||||||
key={`${service.titleKey}-${index}`}
|
|
||||||
id={`service-${index}`}
|
|
||||||
label={`Service ${index + 1}`}
|
|
||||||
isActive={selectedBlockId === `service-${index}`}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={service.cardStyleClass || SERVICE_CARD_STYLE_DEFAULT}>
|
|
||||||
<PageIcon
|
|
||||||
name={service.icon}
|
|
||||||
className={`mb-6 h-12 w-12 ${getIconColor(index)}`}
|
|
||||||
/>
|
|
||||||
<h3 className={service.titleStyleClass || SERVICE_TITLE_STYLE_DEFAULT}>
|
|
||||||
{service.title}
|
|
||||||
</h3>
|
|
||||||
<p className={service.descriptionStyleClass || SERVICE_DESCRIPTION_STYLE_DEFAULT}>
|
|
||||||
{service.description}
|
|
||||||
</p>
|
|
||||||
<ul className="space-y-2">
|
|
||||||
{service.features.map((feature, featureIndex) => (
|
|
||||||
<li
|
|
||||||
key={feature.key || featureIndex}
|
|
||||||
className={service.featureStyleClass || SERVICE_FEATURE_STYLE_DEFAULT}
|
|
||||||
>
|
|
||||||
<span className="mr-2 h-2 w-2 rounded-full bg-blue-600" />
|
|
||||||
{feature.value}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Support plans */}
|
|
||||||
<div className="bg-white py-10 dark:bg-gray-900">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<SelectableBlock
|
|
||||||
id="support-heading"
|
|
||||||
label="Support"
|
|
||||||
isActive={selectedBlockId === 'support-heading'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<h2 className={content?.supportTitleStyleClass || SUPPORT_HEADING_STYLE_DEFAULT}>
|
|
||||||
{content?.supportTitle}
|
|
||||||
</h2>
|
|
||||||
</SelectableBlock>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-3">
|
|
||||||
{content?.supportItems.map((plan, index) => (
|
|
||||||
<SelectableBlock
|
|
||||||
key={`${plan.titleKey}-${index}`}
|
|
||||||
id={`support-${index}`}
|
|
||||||
label={`Support ${index + 1}`}
|
|
||||||
isActive={selectedBlockId === `support-${index}`}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={plan.cardStyleClass || SUPPORT_CARD_STYLE_DEFAULT}>
|
|
||||||
<PageIcon name={plan.icon} className={`mb-6 h-12 w-12 ${getIconColor(index)}`} />
|
|
||||||
<h3 className={plan.titleStyleClass || SUPPORT_TITLE_STYLE_DEFAULT}>
|
|
||||||
{plan.title}
|
|
||||||
</h3>
|
|
||||||
<ul className="mb-8 space-y-3">
|
|
||||||
{plan.features.map((feature, featureIndex) => (
|
|
||||||
<li
|
|
||||||
key={feature.key || featureIndex}
|
|
||||||
className="flex items-center space-x-2 text-gray-700 dark:text-gray-300"
|
|
||||||
>
|
|
||||||
<FaCheckCircle className="h-5 w-5 flex-shrink-0 text-green-500" />
|
|
||||||
<span>{feature.value}</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.contact}
|
|
||||||
className={content?.supportButtonStyleClass || SUPPORT_BUTTON_STYLE_DEFAULT}
|
|
||||||
>
|
|
||||||
{content?.supportButtonLabel}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* CTA */}
|
|
||||||
<SelectableBlock
|
|
||||||
id="cta"
|
|
||||||
label="CTA"
|
|
||||||
isActive={selectedBlockId === 'cta'}
|
|
||||||
isDesignMode={isDesignMode}
|
|
||||||
onSelect={selectBlock}
|
|
||||||
>
|
|
||||||
<div className={content?.ctaSectionStyleClass || CTA_SECTION_STYLE_DEFAULT}>
|
|
||||||
<div className="container mx-auto px-4 text-center">
|
|
||||||
<h2 className={content?.ctaTitleStyleClass || CTA_TITLE_STYLE_DEFAULT}>
|
|
||||||
{content?.ctaTitle}
|
|
||||||
</h2>
|
|
||||||
<p className={content?.ctaDescriptionStyleClass || CTA_DESCRIPTION_STYLE_DEFAULT}>
|
|
||||||
{content?.ctaDescription}
|
|
||||||
</p>
|
|
||||||
<Link
|
|
||||||
to={ROUTES_ENUM.public.contact}
|
|
||||||
className={content?.ctaButtonStyleClass || CTA_BUTTON_STYLE_DEFAULT}
|
|
||||||
>
|
|
||||||
{content?.ctaButtonLabel}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SelectableBlock>
|
|
||||||
|
|
||||||
<DesignerLayer pageTitle="Services" selection={selection} designer={designer} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Services
|
|
||||||
|
|
@ -1,410 +0,0 @@
|
||||||
import React, { useEffect, useMemo, useState } from 'react'
|
|
||||||
import Avatar from '@/components/ui/Avatar'
|
|
||||||
import { Button, Input } from '@/components/ui'
|
|
||||||
import { IconPickerField } from '@/views/shared/MenuAddDialog'
|
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
||||||
import { normalizeLanguageKey, translateLabel } from './localization'
|
|
||||||
import { DesignerField, DesignerFieldValue, DesignerLanguageOption, DesignerSelection } from './types'
|
|
||||||
|
|
||||||
const GROUP_ORDER = ['content', 'media', 'style', 'advanced']
|
|
||||||
const GROUP_FALLBACK_LABELS: Record<string, string> = {
|
|
||||||
content: 'Icerik',
|
|
||||||
media: 'Gorsel',
|
|
||||||
style: 'Stil',
|
|
||||||
advanced: 'Gelismis',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DesignerDrawerProps {
|
|
||||||
isOpen: boolean
|
|
||||||
selection: DesignerSelection | null
|
|
||||||
pageTitle: string
|
|
||||||
selectedLanguage: string
|
|
||||||
languages: DesignerLanguageOption[]
|
|
||||||
isSaving?: boolean
|
|
||||||
isDirty?: boolean
|
|
||||||
canUndo?: boolean
|
|
||||||
canRedo?: boolean
|
|
||||||
onClose: () => void
|
|
||||||
onSave: () => void
|
|
||||||
onLanguageChange: (cultureName: string) => void
|
|
||||||
onReset: () => void
|
|
||||||
onFieldChange: (fieldKey: string, value: DesignerFieldValue) => void
|
|
||||||
onUndo?: () => void
|
|
||||||
onRedo?: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function toDisplayString(value: DesignerFieldValue) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'boolean' || typeof value === 'number') {
|
|
||||||
return String(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return value ?? ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const StyleClassEditor: React.FC<{
|
|
||||||
field: DesignerField
|
|
||||||
onChange: (value: string) => void
|
|
||||||
}> = ({ field, onChange }) => {
|
|
||||||
const tokens = toDisplayString(field.value).split(/\s+/).filter(Boolean)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Input
|
|
||||||
textArea
|
|
||||||
rows={field.rows ?? 3}
|
|
||||||
value={toDisplayString(field.value)}
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange(event.target.value)}
|
|
||||||
/>
|
|
||||||
{tokens.length > 0 && (
|
|
||||||
<div className="flex flex-wrap gap-1">
|
|
||||||
{tokens.map((token, index) => (
|
|
||||||
<button
|
|
||||||
key={`${token}-${index}`}
|
|
||||||
type="button"
|
|
||||||
title="Kaldir"
|
|
||||||
className="rounded-full bg-slate-100 px-2 py-0.5 font-mono text-[11px] text-slate-600 transition-colors hover:bg-rose-100 hover:text-rose-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-rose-900/40 dark:hover:text-rose-300"
|
|
||||||
onClick={() => onChange(tokens.filter((_, i) => i !== index).join(' '))}
|
|
||||||
>
|
|
||||||
{token}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ImageFieldEditor: React.FC<{
|
|
||||||
field: DesignerField
|
|
||||||
onChange: (value: string) => void
|
|
||||||
}> = ({ field, onChange }) => {
|
|
||||||
const value = toDisplayString(field.value)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Input
|
|
||||||
value={value}
|
|
||||||
placeholder={field.placeholder ?? 'https://...'}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange(event.target.value)}
|
|
||||||
/>
|
|
||||||
{value && (
|
|
||||||
<div className="overflow-hidden rounded-lg border border-slate-200 bg-slate-50 dark:border-gray-700 dark:bg-gray-800">
|
|
||||||
<img
|
|
||||||
src={value}
|
|
||||||
alt={field.label}
|
|
||||||
loading="lazy"
|
|
||||||
className="h-28 w-full object-cover"
|
|
||||||
onError={(event) => {
|
|
||||||
event.currentTarget.style.display = 'none'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const FieldEditor: React.FC<{
|
|
||||||
field: DesignerField
|
|
||||||
onChange: (fieldKey: string, value: DesignerFieldValue) => void
|
|
||||||
}> = ({ field, onChange }) => {
|
|
||||||
switch (field.type) {
|
|
||||||
case 'icon':
|
|
||||||
return (
|
|
||||||
<IconPickerField
|
|
||||||
value={toDisplayString(field.value)}
|
|
||||||
onChange={(iconKey) => onChange(field.key, iconKey)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'image':
|
|
||||||
return <ImageFieldEditor field={field} onChange={(value) => onChange(field.key, value)} />
|
|
||||||
case 'style':
|
|
||||||
return <StyleClassEditor field={field} onChange={(value) => onChange(field.key, value)} />
|
|
||||||
case 'boolean':
|
|
||||||
return (
|
|
||||||
<label className="flex cursor-pointer items-center gap-2 text-sm text-slate-600 dark:text-gray-300">
|
|
||||||
<Input
|
|
||||||
unstyle
|
|
||||||
type="checkbox"
|
|
||||||
checked={field.value === true || field.value === 'true'}
|
|
||||||
className="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500 dark:border-gray-600"
|
|
||||||
onChange={(event) => onChange(field.key, event.target.checked)}
|
|
||||||
/>
|
|
||||||
{field.placeholder ?? field.label}
|
|
||||||
</label>
|
|
||||||
)
|
|
||||||
case 'number':
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={toDisplayString(field.value)}
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const parsed = Number(event.target.value)
|
|
||||||
onChange(field.key, Number.isFinite(parsed) ? parsed : 0)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'list':
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
textArea
|
|
||||||
rows={field.rows ?? 5}
|
|
||||||
value={toDisplayString(field.value)}
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
onChange(
|
|
||||||
field.key,
|
|
||||||
event.target.value
|
|
||||||
.split('\n')
|
|
||||||
.map((item) => item.trim())
|
|
||||||
.filter(Boolean),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'textarea':
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
textArea
|
|
||||||
rows={field.rows ?? 4}
|
|
||||||
value={toDisplayString(field.value)}
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
onChange(field.key, event.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
value={toDisplayString(field.value)}
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
onChange(field.key, event.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const DesignerDrawer: React.FC<DesignerDrawerProps> = ({
|
|
||||||
isOpen,
|
|
||||||
selection,
|
|
||||||
pageTitle,
|
|
||||||
selectedLanguage,
|
|
||||||
languages,
|
|
||||||
isSaving = false,
|
|
||||||
isDirty = false,
|
|
||||||
canUndo = false,
|
|
||||||
canRedo = false,
|
|
||||||
onClose,
|
|
||||||
onSave,
|
|
||||||
onLanguageChange,
|
|
||||||
onReset,
|
|
||||||
onFieldChange,
|
|
||||||
onUndo,
|
|
||||||
onRedo,
|
|
||||||
}) => {
|
|
||||||
const { translate } = useLocalization()
|
|
||||||
const [search, setSearch] = useState('')
|
|
||||||
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSearch('')
|
|
||||||
}, [selection?.id])
|
|
||||||
|
|
||||||
const groupedFields = useMemo(() => {
|
|
||||||
const query = search.trim().toLowerCase()
|
|
||||||
const fields = (selection?.fields ?? []).filter(
|
|
||||||
(field) =>
|
|
||||||
!query ||
|
|
||||||
field.label.toLowerCase().includes(query) ||
|
|
||||||
field.key.toLowerCase().includes(query),
|
|
||||||
)
|
|
||||||
|
|
||||||
const groups = new Map<string, DesignerField[]>()
|
|
||||||
|
|
||||||
fields.forEach((field) => {
|
|
||||||
const group = field.group ?? 'content'
|
|
||||||
groups.set(group, [...(groups.get(group) ?? []), field])
|
|
||||||
})
|
|
||||||
|
|
||||||
return Array.from(groups.entries()).sort(([a], [b]) => {
|
|
||||||
const indexA = GROUP_ORDER.indexOf(a)
|
|
||||||
const indexB = GROUP_ORDER.indexOf(b)
|
|
||||||
|
|
||||||
return (indexA === -1 ? GROUP_ORDER.length : indexA) - (indexB === -1 ? GROUP_ORDER.length : indexB)
|
|
||||||
})
|
|
||||||
}, [search, selection])
|
|
||||||
|
|
||||||
const totalFieldCount = selection?.fields.length ?? 0
|
|
||||||
|
|
||||||
if (!isOpen) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<aside className="fixed inset-y-0 right-0 z-50 flex w-full max-w-[420px] flex-col border-l border-slate-200 bg-white shadow-2xl dark:border-gray-700 dark:bg-gray-900">
|
|
||||||
<header className="border-b border-slate-200 px-5 py-4 dark:border-gray-700">
|
|
||||||
<div className="flex items-start justify-between gap-4">
|
|
||||||
<div className="min-w-0">
|
|
||||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-sky-700 dark:text-sky-400">
|
|
||||||
{pageTitle} {translateLabel(translate, 'App.PublicDesigner.DesignerTitle')}
|
|
||||||
</p>
|
|
||||||
<h3 className="mt-2 truncate text-lg font-semibold text-slate-900 dark:text-gray-100">
|
|
||||||
{selection?.title ??
|
|
||||||
translateLabel(translate, 'App.PublicDesigner.NoSelection')}
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-sm text-slate-500 dark:text-gray-400">
|
|
||||||
{selection?.description ??
|
|
||||||
translateLabel(translate, 'App.PublicDesigner.SelectField')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button variant="plain" size="sm" onClick={onClose}>
|
|
||||||
{translateLabel(translate, 'App.Platform.Hide')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-4 flex flex-wrap items-center gap-1.5">
|
|
||||||
{languages.map((language) => (
|
|
||||||
<Button
|
|
||||||
key={`${language.cultureName}-${language.key}`}
|
|
||||||
type="button"
|
|
||||||
variant="plain"
|
|
||||||
shape="circle"
|
|
||||||
title={language.displayName}
|
|
||||||
aria-label={language.displayName}
|
|
||||||
aria-pressed={normalizeLanguageKey(selectedLanguage) === language.key}
|
|
||||||
className={`!h-9 !w-9 !px-0 border transition-all ${
|
|
||||||
normalizeLanguageKey(selectedLanguage) === language.key
|
|
||||||
? 'border-sky-500 bg-sky-50 ring-2 ring-sky-200 dark:bg-sky-900/20 dark:ring-sky-700'
|
|
||||||
: 'border-slate-200 hover:border-slate-400 hover:bg-slate-50 dark:border-gray-700 dark:hover:border-gray-500 dark:hover:bg-gray-800'
|
|
||||||
}`}
|
|
||||||
onClick={() => onLanguageChange(language.cultureName)}
|
|
||||||
>
|
|
||||||
<Avatar size={22} shape="circle" src={`/img/countries/${language.cultureName}.png`} />
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<span className="ml-auto flex items-center gap-1">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="plain"
|
|
||||||
size="sm"
|
|
||||||
disabled={!canUndo}
|
|
||||||
title={translateLabel(translate, 'Public.designer.undo')}
|
|
||||||
onClick={onUndo}
|
|
||||||
>
|
|
||||||
↶
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="plain"
|
|
||||||
size="sm"
|
|
||||||
disabled={!canRedo}
|
|
||||||
title={translateLabel(translate, 'Public.designer.redo')}
|
|
||||||
onClick={onRedo}
|
|
||||||
>
|
|
||||||
↷
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{totalFieldCount > 6 && (
|
|
||||||
<div className="mt-3">
|
|
||||||
<Input
|
|
||||||
size="sm"
|
|
||||||
value={search}
|
|
||||||
placeholder={translateLabel(translate, 'Public.designer.searchField')}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setSearch(event.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="flex-1 space-y-5 overflow-y-auto px-5 py-5">
|
|
||||||
{!selection && (
|
|
||||||
<div className="rounded-2xl border border-dashed border-slate-300 bg-slate-50 px-4 py-6 text-sm text-slate-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400">
|
|
||||||
{translateLabel(translate, 'App.PublicDesigner.NoSelectionDetails')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selection && groupedFields.length === 0 && (
|
|
||||||
<p className="text-sm text-slate-500 dark:text-gray-400">
|
|
||||||
{translateLabel(translate, 'App.Platform.NoDataFound')}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{groupedFields.map(([group, fields]) => {
|
|
||||||
const isCollapsed = collapsedGroups[group] === true
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section key={group}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="mb-2 flex w-full items-center justify-between text-xs font-semibold uppercase tracking-[0.14em] text-slate-400 transition-colors hover:text-slate-600 dark:text-gray-500 dark:hover:text-gray-300"
|
|
||||||
onClick={() =>
|
|
||||||
setCollapsedGroups((previous) => ({ ...previous, [group]: !isCollapsed }))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{translateLabel(
|
|
||||||
translate,
|
|
||||||
`Public.designer.group.${group}`,
|
|
||||||
GROUP_FALLBACK_LABELS[group] ?? group,
|
|
||||||
)}
|
|
||||||
<span className="ml-2 font-normal normal-case tracking-normal">
|
|
||||||
({fields.length})
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span>{isCollapsed ? '+' : '−'}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{!isCollapsed && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
{fields.map((field) => (
|
|
||||||
<div key={field.key}>
|
|
||||||
<label className="mb-1 block text-sm font-semibold text-slate-700 dark:text-gray-200">
|
|
||||||
{field.label}
|
|
||||||
</label>
|
|
||||||
{field.helpText && (
|
|
||||||
<p className="mb-2 text-xs text-slate-500 dark:text-gray-400">
|
|
||||||
{field.helpText}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<FieldEditor field={field} onChange={onFieldChange} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer className="flex items-center gap-2 border-t border-slate-200 px-5 py-4 dark:border-gray-700">
|
|
||||||
<Button type="button" variant="plain" disabled={!isDirty || isSaving} onClick={onReset}>
|
|
||||||
{translateLabel(translate, 'Public.designer.reset')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
block
|
|
||||||
type="button"
|
|
||||||
variant="solid"
|
|
||||||
loading={isSaving}
|
|
||||||
disabled={!isDirty || isSaving}
|
|
||||||
onClick={onSave}
|
|
||||||
>
|
|
||||||
{translate('::App.Platform.Save')}
|
|
||||||
</Button>
|
|
||||||
</footer>
|
|
||||||
</aside>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DesignerDrawer
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import { Button } from '@/components/ui'
|
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
||||||
import DesignerDrawer from './DesignerDrawer'
|
|
||||||
import { translateLabel } from './localization'
|
|
||||||
import { DesignerFieldValue, DesignerLanguageOption, DesignerSelection } from './types'
|
|
||||||
|
|
||||||
export interface DesignerLayerController {
|
|
||||||
isDesignMode: boolean
|
|
||||||
isPanelVisible: boolean
|
|
||||||
isSaving: boolean
|
|
||||||
isDirty: boolean
|
|
||||||
canUndo: boolean
|
|
||||||
canRedo: boolean
|
|
||||||
selectedLanguage: string
|
|
||||||
drawerLanguages: DesignerLanguageOption[]
|
|
||||||
setIsPanelVisible: (visible: boolean) => void
|
|
||||||
changeLanguage: (cultureName: string) => void
|
|
||||||
changeField: (fieldKey: string, value: DesignerFieldValue) => void
|
|
||||||
resetContent: () => void
|
|
||||||
exitDesignMode: () => void
|
|
||||||
save: () => void
|
|
||||||
undo: () => void
|
|
||||||
redo: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DesignerLayerProps {
|
|
||||||
pageTitle: string
|
|
||||||
selection: DesignerSelection | null
|
|
||||||
designer: DesignerLayerController
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tasarim modunun sayfadan bagimsiz arayuzu: durum cubugu ve duzenleme paneli.
|
|
||||||
* Tasarim modu kapaliyken hicbir sey render etmez.
|
|
||||||
*/
|
|
||||||
const DesignerLayer: React.FC<DesignerLayerProps> = ({ pageTitle, selection, designer }) => {
|
|
||||||
const { translate } = useLocalization()
|
|
||||||
|
|
||||||
if (!designer.isDesignMode) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="fixed bottom-6 left-6 z-40 flex items-center gap-2 rounded-full bg-slate-900/95 px-3 py-2 text-sm font-medium text-white shadow-xl backdrop-blur">
|
|
||||||
<span className="pl-1">{pageTitle}</span>
|
|
||||||
<span
|
|
||||||
className={`h-2 w-2 rounded-full ${designer.isDirty ? 'bg-amber-400' : 'bg-emerald-400'}`}
|
|
||||||
title={
|
|
||||||
designer.isDirty
|
|
||||||
? translateLabel(translate, 'Public.designer.unsaved')
|
|
||||||
: translateLabel(translate, 'Public.designer.saved')
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="plain"
|
|
||||||
size="sm"
|
|
||||||
className="!text-white"
|
|
||||||
onClick={() => designer.setIsPanelVisible(!designer.isPanelVisible)}
|
|
||||||
>
|
|
||||||
{designer.isPanelVisible
|
|
||||||
? translateLabel(translate, 'Public.designer.hidePanel')
|
|
||||||
: translateLabel(translate, 'App.PublicDesigner.ShowPanel')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="plain"
|
|
||||||
size="sm"
|
|
||||||
className="!text-white"
|
|
||||||
onClick={designer.exitDesignMode}
|
|
||||||
>
|
|
||||||
{translateLabel(translate, 'Public.designer.exit')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DesignerDrawer
|
|
||||||
isOpen={designer.isPanelVisible}
|
|
||||||
selection={selection}
|
|
||||||
pageTitle={pageTitle}
|
|
||||||
selectedLanguage={designer.selectedLanguage}
|
|
||||||
languages={designer.drawerLanguages}
|
|
||||||
isSaving={designer.isSaving}
|
|
||||||
isDirty={designer.isDirty}
|
|
||||||
canUndo={designer.canUndo}
|
|
||||||
canRedo={designer.canRedo}
|
|
||||||
onClose={() => designer.setIsPanelVisible(false)}
|
|
||||||
onSave={designer.save}
|
|
||||||
onLanguageChange={designer.changeLanguage}
|
|
||||||
onReset={designer.resetContent}
|
|
||||||
onFieldChange={designer.changeField}
|
|
||||||
onUndo={designer.undo}
|
|
||||||
onRedo={designer.redo}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DesignerLayer
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
import classNames from 'classnames'
|
|
||||||
import React, { PropsWithChildren, useCallback } from 'react'
|
|
||||||
|
|
||||||
interface SelectableBlockProps extends PropsWithChildren {
|
|
||||||
id: string
|
|
||||||
isActive: boolean
|
|
||||||
isDesignMode: boolean
|
|
||||||
onSelect: (id: string) => void
|
|
||||||
/** Blok uzerinde gorunen etiket. Verilmezse `id` kullanilir. */
|
|
||||||
label?: string
|
|
||||||
className?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tasarim modunda icerigi secilebilir hale getirir.
|
|
||||||
* Tasarim modu kapaliyken hicbir sarmalayici DOM dugumu uretmez.
|
|
||||||
*/
|
|
||||||
const SelectableBlock: React.FC<SelectableBlockProps> = ({
|
|
||||||
id,
|
|
||||||
isActive,
|
|
||||||
isDesignMode,
|
|
||||||
onSelect,
|
|
||||||
label,
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const handleSelect = useCallback(
|
|
||||||
(event: React.SyntheticEvent) => {
|
|
||||||
// Tasarim modunda blok icindeki link/buton tiklamalari sayfayi degistirmemeli;
|
|
||||||
// tiklama yalnizca blogu secmeli.
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
onSelect(id)
|
|
||||||
},
|
|
||||||
[id, onSelect],
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!isDesignMode) {
|
|
||||||
return <>{children}</>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
'group/designer relative rounded-xl transition-all cursor-pointer',
|
|
||||||
isActive
|
|
||||||
? 'ring-2 ring-sky-500 ring-offset-4 ring-offset-white dark:ring-offset-gray-950'
|
|
||||||
: 'ring-1 ring-sky-200/80 hover:ring-sky-400 dark:ring-sky-700/80 dark:hover:ring-sky-500',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
aria-pressed={isActive}
|
|
||||||
aria-label={label ?? id}
|
|
||||||
onClickCapture={handleSelect}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === 'Enter' || event.key === ' ') {
|
|
||||||
handleSelect(event)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={classNames(
|
|
||||||
'pointer-events-none absolute -top-2.5 left-3 z-20 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider shadow-sm transition-opacity',
|
|
||||||
isActive
|
|
||||||
? 'bg-sky-500 text-white opacity-100'
|
|
||||||
: 'bg-slate-900/80 text-white opacity-0 group-hover/designer:opacity-100',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{label ?? id}
|
|
||||||
</span>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SelectableBlock
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import { DesignerField } from './types'
|
|
||||||
|
|
||||||
interface FieldExtras {
|
|
||||||
group?: string
|
|
||||||
placeholder?: string
|
|
||||||
helpText?: string
|
|
||||||
rows?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
function build(
|
|
||||||
type: DesignerField['type'],
|
|
||||||
key: string,
|
|
||||||
label: string,
|
|
||||||
value: DesignerField['value'],
|
|
||||||
extras?: FieldExtras,
|
|
||||||
): DesignerField {
|
|
||||||
return { key, label, type, value, ...extras }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const textField = (key: string, label: string, value: string, extras?: FieldExtras) =>
|
|
||||||
build('text', key, label, value, extras)
|
|
||||||
|
|
||||||
export const textAreaField = (key: string, label: string, value: string, extras?: FieldExtras) =>
|
|
||||||
build('textarea', key, label, value, extras)
|
|
||||||
|
|
||||||
export const imageField = (key: string, label: string, value: string, extras?: FieldExtras) =>
|
|
||||||
build('image', key, label, value, extras)
|
|
||||||
|
|
||||||
export const iconField = (key: string, label: string, value: string, extras?: FieldExtras) =>
|
|
||||||
build('icon', key, label, value, extras)
|
|
||||||
|
|
||||||
export const numberField = (key: string, label: string, value: number, extras?: FieldExtras) =>
|
|
||||||
build('number', key, label, value, extras)
|
|
||||||
|
|
||||||
export const booleanField = (key: string, label: string, value: boolean, extras?: FieldExtras) =>
|
|
||||||
build('boolean', key, label, value, extras)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tailwind sinif listesi alani. Panelde tek satirlik editor + sinif rozetleri ile gosterilir.
|
|
||||||
*/
|
|
||||||
export const styleField = (key: string, label: string, value: string, extras?: FieldExtras) =>
|
|
||||||
build('style', key, label, value, { group: extras?.group ?? 'style', ...extras })
|
|
||||||
|
|
||||||
/** Alan gruplarinin panelde gorunecegi sira. */
|
|
||||||
export const FIELD_GROUP_ORDER = ['content', 'media', 'style', 'advanced'] as const
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
export { default as DesignerDrawer } from './DesignerDrawer'
|
|
||||||
export { default as DesignerLayer } from './DesignerLayer'
|
|
||||||
export { default as SelectableBlock } from './SelectableBlock'
|
|
||||||
export * from './fields'
|
|
||||||
export * from './localization'
|
|
||||||
export * from './paths'
|
|
||||||
export * from './types'
|
|
||||||
export * from './useDesignerLanguages'
|
|
||||||
export { useDesignerState } from './useDesignerState'
|
|
||||||
export { usePageDesigner } from './usePageDesigner'
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
export type TranslateFn = (key: string) => string
|
|
||||||
|
|
||||||
const LOCALIZATION_KEY_PATTERN = /^[A-Za-z0-9_.-]+$/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bir metnin lokalizasyon anahtari mi yoksa duz metin mi oldugunu tahmin eder.
|
|
||||||
* Anahtarlar bosluk icermez ve en az bir nokta ile ayrilmis segment tasir.
|
|
||||||
*/
|
|
||||||
export function isLikelyLocalizationKey(value?: string) {
|
|
||||||
return Boolean(value && LOCALIZATION_KEY_PATTERN.test(value) && value.includes('.'))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deger bir lokalizasyon anahtari ise cevirisini, degilse degerin kendisini dondurur.
|
|
||||||
* Ceviri bulunamadiginda `fallback` kullanilir.
|
|
||||||
*/
|
|
||||||
export function resolveLocalizedValue(
|
|
||||||
translate: TranslateFn,
|
|
||||||
keyOrValue: string | undefined,
|
|
||||||
fallback = '',
|
|
||||||
) {
|
|
||||||
if (!keyOrValue) {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLikelyLocalizationKey(keyOrValue)) {
|
|
||||||
return keyOrValue
|
|
||||||
}
|
|
||||||
|
|
||||||
const translatedValue = translate('::' + keyOrValue)
|
|
||||||
|
|
||||||
return translatedValue === keyOrValue ? fallback || keyOrValue : translatedValue
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Designer arayuz metinleri icin: anahtar tanimli degilse okunabilir bir yedek metin dondurur.
|
|
||||||
*/
|
|
||||||
export function translateLabel(translate: TranslateFn, key: string, fallback?: string) {
|
|
||||||
const value = translate('::' + key)
|
|
||||||
|
|
||||||
return value === key ? (fallback ?? key) : value
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeLanguageKey(language?: string) {
|
|
||||||
if (!language) {
|
|
||||||
return 'tr'
|
|
||||||
}
|
|
||||||
|
|
||||||
return language.toLowerCase().split('-')[0]
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
type AnyRecord = Record<string, unknown>
|
|
||||||
|
|
||||||
function splitPath(path: string) {
|
|
||||||
return path.split('.').filter((segment) => segment.length > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isIndexSegment(segment: string) {
|
|
||||||
return /^\d+$/.test(segment)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** `slides.0.services.1.icon` gibi bir yolu okur. */
|
|
||||||
export function getByPath(source: unknown, path: string): unknown {
|
|
||||||
return splitPath(path).reduce<unknown>((accumulator, segment) => {
|
|
||||||
if (accumulator === null || typeof accumulator !== 'object') {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return (accumulator as AnyRecord)[segment]
|
|
||||||
}, source)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Yol boyunca yalnizca degisen dugumleri kopyalayarak yeni bir nesne dondurur.
|
|
||||||
* Yol gecersizse (ornegin dizi indeksi yoksa) kaynak nesne degistirilmeden dondurulur.
|
|
||||||
*/
|
|
||||||
export function setByPath<T>(source: T, path: string, value: unknown): T {
|
|
||||||
const segments = splitPath(path)
|
|
||||||
|
|
||||||
if (segments.length === 0 || source === null || typeof source !== 'object') {
|
|
||||||
return source
|
|
||||||
}
|
|
||||||
|
|
||||||
const [segment, ...rest] = segments
|
|
||||||
const container = source as unknown as AnyRecord
|
|
||||||
|
|
||||||
if (Array.isArray(source)) {
|
|
||||||
if (!isIndexSegment(segment)) {
|
|
||||||
return source
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = Number(segment)
|
|
||||||
|
|
||||||
if (index < 0 || index >= source.length) {
|
|
||||||
return source
|
|
||||||
}
|
|
||||||
|
|
||||||
const next = [...(source as unknown[])]
|
|
||||||
next[index] = rest.length === 0 ? value : setByPath(next[index], rest.join('.'), value)
|
|
||||||
|
|
||||||
return next as unknown as T
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(segment in container)) {
|
|
||||||
return source
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...container,
|
|
||||||
[segment]: rest.length === 0 ? value : setByPath(container[segment], rest.join('.'), value),
|
|
||||||
} as unknown as T
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
export type DesignerFieldType =
|
|
||||||
| 'text'
|
|
||||||
| 'textarea'
|
|
||||||
| 'image'
|
|
||||||
| 'list'
|
|
||||||
| 'icon'
|
|
||||||
| 'style'
|
|
||||||
| 'number'
|
|
||||||
| 'boolean'
|
|
||||||
|
|
||||||
export type DesignerFieldValue = string | string[] | number | boolean
|
|
||||||
|
|
||||||
export interface DesignerField {
|
|
||||||
/**
|
|
||||||
* Duzenlenen icerik nesnesine giden nokta yolu.
|
|
||||||
* Ornek: `heroTitle`, `slides.0.title`, `slides.0.services.2.icon`
|
|
||||||
*/
|
|
||||||
key: string
|
|
||||||
label: string
|
|
||||||
type: DesignerFieldType
|
|
||||||
value: DesignerFieldValue
|
|
||||||
/** Panelde alanlari gruplamak icin kullanilir. */
|
|
||||||
group?: string
|
|
||||||
placeholder?: string
|
|
||||||
helpText?: string
|
|
||||||
rows?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DesignerSelection {
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
fields: DesignerField[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DesignerLanguageOption {
|
|
||||||
key: string
|
|
||||||
cultureName: string
|
|
||||||
displayName: string
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
import { useMemo } from 'react'
|
|
||||||
import { useStoreState } from '@/store'
|
|
||||||
import { normalizeLanguageKey } from './localization'
|
|
||||||
import { DesignerLanguageOption } from './types'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ABP yapilandirmasindaki dilleri designer paneli icin normalize eder.
|
|
||||||
* Dort public sayfada tekrarlanan dil hazirligi burada tek noktada toplanir.
|
|
||||||
*/
|
|
||||||
export function useDesignerLanguages() {
|
|
||||||
const configCultureName = useStoreState(
|
|
||||||
(state) => state.abpConfig.config?.localization.currentCulture.cultureName,
|
|
||||||
)
|
|
||||||
const localeCurrentLang = useStoreState((state) => state.locale?.currentLang)
|
|
||||||
const abpLanguages = useStoreState((state) => state.abpConfig.config?.localization.languages)
|
|
||||||
|
|
||||||
const currentLanguage = configCultureName || localeCurrentLang || 'tr'
|
|
||||||
|
|
||||||
const languageOptions = useMemo<DesignerLanguageOption[]>(
|
|
||||||
() =>
|
|
||||||
(abpLanguages ?? [])
|
|
||||||
.filter((language) => Boolean(language.cultureName))
|
|
||||||
.map((language) => {
|
|
||||||
const cultureName = language.cultureName as string
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: normalizeLanguageKey(cultureName),
|
|
||||||
cultureName,
|
|
||||||
displayName: language.displayName || cultureName,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
[abpLanguages],
|
|
||||||
)
|
|
||||||
|
|
||||||
const editorLanguages = useMemo(
|
|
||||||
() =>
|
|
||||||
Array.from(
|
|
||||||
new Set(
|
|
||||||
(languageOptions.length > 0
|
|
||||||
? languageOptions.map((language) => language.key)
|
|
||||||
: [normalizeLanguageKey(currentLanguage)]
|
|
||||||
).filter(Boolean),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
[currentLanguage, languageOptions],
|
|
||||||
)
|
|
||||||
|
|
||||||
return { currentLanguage, languageOptions, editorLanguages }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Panelde gosterilecek dil listesi; yapilandirma bos ise desteklenen dillerden uretilir. */
|
|
||||||
export function resolveDrawerLanguages(
|
|
||||||
languageOptions: DesignerLanguageOption[],
|
|
||||||
supportedLanguages: string[],
|
|
||||||
): DesignerLanguageOption[] {
|
|
||||||
if (languageOptions.length > 0) {
|
|
||||||
return languageOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
return supportedLanguages.map((language) => ({
|
|
||||||
key: language,
|
|
||||||
cultureName: language,
|
|
||||||
displayName: language.toUpperCase(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
@ -1,272 +0,0 @@
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
||||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
|
|
||||||
import { normalizeLanguageKey } from './localization'
|
|
||||||
import { setByPath } from './paths'
|
|
||||||
|
|
||||||
const DESIGNER_QUERY_KEY = 'design'
|
|
||||||
const HISTORY_LIMIT = 50
|
|
||||||
|
|
||||||
interface UseDesignerStateOptions {
|
|
||||||
currentLanguage?: string
|
|
||||||
supportedLanguages?: string[]
|
|
||||||
/** Tasarim modunun acilabilmesi icin yetki kontrolu. Varsayilan: acik. */
|
|
||||||
enabled?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LanguageEntry<T> {
|
|
||||||
current: T
|
|
||||||
/** Sunucudan gelen son icerigin serilestirilmis hali; "kirli mi" karsilastirmasi icin. */
|
|
||||||
baseline: string
|
|
||||||
past: T[]
|
|
||||||
future: T[]
|
|
||||||
}
|
|
||||||
|
|
||||||
function serialize(value: unknown) {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(value) ?? '__undefined__'
|
|
||||||
} catch {
|
|
||||||
return '__unserializable__'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clone<T>(value: T): T {
|
|
||||||
try {
|
|
||||||
return JSON.parse(JSON.stringify(value)) as T
|
|
||||||
} catch {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createEntry<T>(content: T): LanguageEntry<T> {
|
|
||||||
return { current: clone(content), baseline: serialize(content), past: [], future: [] }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDesignerState<T>(
|
|
||||||
pageKey: string,
|
|
||||||
initialContent: T | null,
|
|
||||||
options?: UseDesignerStateOptions,
|
|
||||||
) {
|
|
||||||
const location = useLocation()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
|
||||||
const isDesignerRequested =
|
|
||||||
searchParams.get(DESIGNER_QUERY_KEY) === '1' || location.pathname.endsWith('/designer')
|
|
||||||
const isDesignMode = isDesignerRequested && options?.enabled !== false
|
|
||||||
|
|
||||||
const currentLanguage = normalizeLanguageKey(options?.currentLanguage)
|
|
||||||
const supportedLanguages = useMemo(() => {
|
|
||||||
const base = (options?.supportedLanguages ?? [currentLanguage]).map(normalizeLanguageKey)
|
|
||||||
|
|
||||||
return Array.from(new Set(base.includes(currentLanguage) ? base : [currentLanguage, ...base]))
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [currentLanguage, serialize(options?.supportedLanguages)])
|
|
||||||
|
|
||||||
const [entries, setEntries] = useState<Record<string, LanguageEntry<T>>>({})
|
|
||||||
const [selectedBlockId, setSelectedBlockId] = useState<string | null>(null)
|
|
||||||
const entriesRef = useRef(entries)
|
|
||||||
entriesRef.current = entries
|
|
||||||
|
|
||||||
const serializedInitial = serialize(initialContent)
|
|
||||||
|
|
||||||
// Sunucu icerigi degistiginde (ilk yukleme veya dil degisimi) o dilin taslagini tohumla.
|
|
||||||
// Kullanici o dilde duzenleme yaptiysa taslak korunur, uzerine yazilmaz.
|
|
||||||
useEffect(() => {
|
|
||||||
if (!initialContent) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setEntries((previous) => {
|
|
||||||
const entry = previous[currentLanguage]
|
|
||||||
|
|
||||||
if (entry && serialize(entry.current) !== entry.baseline) {
|
|
||||||
return previous
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry && entry.baseline === serializedInitial) {
|
|
||||||
return previous
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...previous, [currentLanguage]: createEntry(initialContent) }
|
|
||||||
})
|
|
||||||
// initialContent yerine serilestirilmis hali izlenir: her render'da yeni referans gelse bile
|
|
||||||
// efekt yalnizca icerik gercekten degistiginde calisir.
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [currentLanguage, serializedInitial])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isDesignMode) {
|
|
||||||
setSelectedBlockId(null)
|
|
||||||
}
|
|
||||||
}, [isDesignMode])
|
|
||||||
|
|
||||||
const entry = entries[currentLanguage]
|
|
||||||
const content = entry?.current ?? initialContent ?? null
|
|
||||||
const isDirty = Boolean(entry) && serialize(entry.current) !== entry.baseline
|
|
||||||
|
|
||||||
const mutate = useCallback(
|
|
||||||
(updater: (current: T) => T | null) => {
|
|
||||||
setEntries((previous) => {
|
|
||||||
const existing = previous[currentLanguage]
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
return previous
|
|
||||||
}
|
|
||||||
|
|
||||||
const next = updater(existing.current)
|
|
||||||
|
|
||||||
if (!next || serialize(next) === serialize(existing.current)) {
|
|
||||||
return previous
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...previous,
|
|
||||||
[currentLanguage]: {
|
|
||||||
...existing,
|
|
||||||
current: next,
|
|
||||||
past: [...existing.past, existing.current].slice(-HISTORY_LIMIT),
|
|
||||||
future: [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[currentLanguage],
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Nokta yolu ile tek bir alani gunceller. */
|
|
||||||
const setFieldValue = useCallback(
|
|
||||||
(path: string, value: unknown) => {
|
|
||||||
mutate((current) => setByPath(current, path, value))
|
|
||||||
},
|
|
||||||
[mutate],
|
|
||||||
)
|
|
||||||
|
|
||||||
const undo = useCallback(() => {
|
|
||||||
setEntries((previous) => {
|
|
||||||
const existing = previous[currentLanguage]
|
|
||||||
|
|
||||||
if (!existing?.past.length) {
|
|
||||||
return previous
|
|
||||||
}
|
|
||||||
|
|
||||||
const past = [...existing.past]
|
|
||||||
const restored = past.pop() as T
|
|
||||||
|
|
||||||
return {
|
|
||||||
...previous,
|
|
||||||
[currentLanguage]: {
|
|
||||||
...existing,
|
|
||||||
current: restored,
|
|
||||||
past,
|
|
||||||
future: [existing.current, ...existing.future].slice(0, HISTORY_LIMIT),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [currentLanguage])
|
|
||||||
|
|
||||||
const redo = useCallback(() => {
|
|
||||||
setEntries((previous) => {
|
|
||||||
const existing = previous[currentLanguage]
|
|
||||||
|
|
||||||
if (!existing?.future.length) {
|
|
||||||
return previous
|
|
||||||
}
|
|
||||||
|
|
||||||
const [restored, ...future] = existing.future
|
|
||||||
|
|
||||||
return {
|
|
||||||
...previous,
|
|
||||||
[currentLanguage]: {
|
|
||||||
...existing,
|
|
||||||
current: restored,
|
|
||||||
past: [...existing.past, existing.current].slice(-HISTORY_LIMIT),
|
|
||||||
future,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [currentLanguage])
|
|
||||||
|
|
||||||
/** Kaydedilmemis degisiklikleri atip sunucudan gelen son icerige doner. */
|
|
||||||
const resetContent = useCallback(() => {
|
|
||||||
setEntries((previous) => {
|
|
||||||
const existing = previous[currentLanguage]
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
return previous
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...previous,
|
|
||||||
[currentLanguage]: {
|
|
||||||
...existing,
|
|
||||||
current: JSON.parse(existing.baseline) as T,
|
|
||||||
past: [...existing.past, existing.current].slice(-HISTORY_LIMIT),
|
|
||||||
future: [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [currentLanguage])
|
|
||||||
|
|
||||||
/** Kaydetme basarili olduktan sonra mevcut icerigi yeni referans nokta yapar. */
|
|
||||||
const commitContent = useCallback(() => {
|
|
||||||
setEntries((previous) => {
|
|
||||||
const existing = previous[currentLanguage]
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
return previous
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...previous,
|
|
||||||
[currentLanguage]: { ...existing, baseline: serialize(existing.current) },
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [currentLanguage])
|
|
||||||
|
|
||||||
const exitDesignMode = useCallback(() => {
|
|
||||||
// Tasarim modu iki sekilde acilabiliyor: `?design=1` veya `/<sayfa>/designer` yolu.
|
|
||||||
if (location.pathname.endsWith('/designer')) {
|
|
||||||
navigate(location.pathname.replace(/\/designer$/, '') || '/', { replace: true })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const next = new URLSearchParams(searchParams)
|
|
||||||
next.delete(DESIGNER_QUERY_KEY)
|
|
||||||
setSearchParams(next, { replace: true })
|
|
||||||
}, [location.pathname, navigate, searchParams, setSearchParams])
|
|
||||||
|
|
||||||
// Tasarim modunda sekme kapatilirken kaydedilmemis degisiklikler icin uyari.
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isDesignMode || !isDirty) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
|
||||||
event.preventDefault()
|
|
||||||
event.returnValue = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
||||||
|
|
||||||
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
||||||
}, [isDesignMode, isDirty])
|
|
||||||
|
|
||||||
return {
|
|
||||||
pageKey,
|
|
||||||
content,
|
|
||||||
isDesignMode,
|
|
||||||
isDirty,
|
|
||||||
canUndo: Boolean(entry?.past.length),
|
|
||||||
canRedo: Boolean(entry?.future.length),
|
|
||||||
selectedBlockId,
|
|
||||||
selectedLanguage: currentLanguage,
|
|
||||||
supportedLanguages,
|
|
||||||
setContent: mutate,
|
|
||||||
setFieldValue,
|
|
||||||
setSelectedBlockId,
|
|
||||||
resetContent,
|
|
||||||
commitContent,
|
|
||||||
exitDesignMode,
|
|
||||||
undo,
|
|
||||||
redo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
|
||||||
import { Notification, toast } from '@/components/ui'
|
|
||||||
import { useStoreActions, useStoreState } from '@/store'
|
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
||||||
import { usePermission } from '@/utils/hooks/usePermission'
|
|
||||||
import { translateLabel } from './localization'
|
|
||||||
import { resolveDrawerLanguages, useDesignerLanguages } from './useDesignerLanguages'
|
|
||||||
import { useDesignerState } from './useDesignerState'
|
|
||||||
import { DesignerFieldValue } from './types'
|
|
||||||
|
|
||||||
interface UsePageDesignerOptions<T> {
|
|
||||||
/** Tasarim panelinde gosterilecek sayfa adi ve state anahtari. */
|
|
||||||
pageKey: string
|
|
||||||
/**
|
|
||||||
* Tasarim modunu acmak icin gereken ABP yetkisi (orn. `App.Home.Design`).
|
|
||||||
* Yetki `PermissionsData.json` icinde tanimlanir ve seeder ile yonetilir.
|
|
||||||
*/
|
|
||||||
permission: string
|
|
||||||
/** Sunucudan uretilen icerik. Referans kararliligi icin `useMemo` ile hesaplanmalidir. */
|
|
||||||
initialContent: T | null
|
|
||||||
/** Kaydetme cagrisi. Basarili donerse icerik yeni referans nokta olarak isaretlenir. */
|
|
||||||
onSave: (content: T, cultureName: string) => Promise<unknown>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Public sayfalarin tasarim modu icin ortak davranis:
|
|
||||||
* dil listesi, panel gorunurlugu, secim, kaydetme ve klavye kisayollari.
|
|
||||||
*/
|
|
||||||
export function usePageDesigner<T>({
|
|
||||||
pageKey,
|
|
||||||
permission,
|
|
||||||
initialContent,
|
|
||||||
onSave,
|
|
||||||
}: UsePageDesignerOptions<T>) {
|
|
||||||
const { translate } = useLocalization()
|
|
||||||
const { setLang } = useStoreActions((actions) => actions.locale)
|
|
||||||
const { getConfig } = useStoreActions((actions) => actions.abpConfig)
|
|
||||||
const isAuthenticated = useStoreState(
|
|
||||||
(state) => state.abpConfig.config?.currentUser?.isAuthenticated,
|
|
||||||
)
|
|
||||||
const { checkPermission } = usePermission()
|
|
||||||
const { currentLanguage, languageOptions, editorLanguages } = useDesignerLanguages()
|
|
||||||
|
|
||||||
// Tasarim modu yalnizca oturum acmis ve ilgili sayfa yetkisi verilmis kullanicilar icin
|
|
||||||
// acilir; anonim ziyaretci ya da yetkisiz kullanici adres cubugundan `?design=1`
|
|
||||||
// ekleyerek paneli acamaz. Asil kontrol sunucuda `PublicAppService.Save*PageAsync`
|
|
||||||
// uzerindeki `[Authorize]` ile yapilir.
|
|
||||||
const canDesign = Boolean(isAuthenticated) && checkPermission(permission)
|
|
||||||
|
|
||||||
const designer = useDesignerState<T>(pageKey, initialContent, {
|
|
||||||
currentLanguage,
|
|
||||||
supportedLanguages: editorLanguages,
|
|
||||||
enabled: canDesign,
|
|
||||||
})
|
|
||||||
|
|
||||||
const [isPanelVisible, setIsPanelVisible] = useState(true)
|
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
|
||||||
|
|
||||||
const {
|
|
||||||
content,
|
|
||||||
isDesignMode,
|
|
||||||
selectedLanguage,
|
|
||||||
setFieldValue,
|
|
||||||
setSelectedBlockId,
|
|
||||||
commitContent,
|
|
||||||
undo,
|
|
||||||
redo,
|
|
||||||
} = designer
|
|
||||||
|
|
||||||
const selectBlock = useCallback(
|
|
||||||
(blockId: string) => {
|
|
||||||
setSelectedBlockId(blockId)
|
|
||||||
setIsPanelVisible(true)
|
|
||||||
},
|
|
||||||
[setSelectedBlockId],
|
|
||||||
)
|
|
||||||
|
|
||||||
const changeLanguage = useCallback(
|
|
||||||
(cultureName: string) => {
|
|
||||||
// Global locale degisimi cevirileri yeniden yukler; designer dili store'u takip eder.
|
|
||||||
setLang(cultureName)
|
|
||||||
},
|
|
||||||
[setLang],
|
|
||||||
)
|
|
||||||
|
|
||||||
const changeField = useCallback(
|
|
||||||
(path: string, value: DesignerFieldValue) => setFieldValue(path, value),
|
|
||||||
[setFieldValue],
|
|
||||||
)
|
|
||||||
|
|
||||||
const save = useCallback(async () => {
|
|
||||||
if (!content || isSaving || !canDesign) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSaving(true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await onSave(content, selectedLanguage)
|
|
||||||
await getConfig(false)
|
|
||||||
commitContent()
|
|
||||||
setSelectedBlockId(null)
|
|
||||||
|
|
||||||
toast.push(
|
|
||||||
<Notification type="success" duration={2000}>
|
|
||||||
{translate('::App.Platform.FormBilgileriKaydedildi')}
|
|
||||||
</Notification>,
|
|
||||||
{ placement: 'bottom-end' },
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`${pageKey} tasarimi kaydedilemedi:`, error)
|
|
||||||
|
|
||||||
toast.push(
|
|
||||||
<Notification type="danger" duration={4000}>
|
|
||||||
{translateLabel(translate, 'Public.designer.saveFailed')}
|
|
||||||
</Notification>,
|
|
||||||
{ placement: 'bottom-end' },
|
|
||||||
)
|
|
||||||
} finally {
|
|
||||||
setIsSaving(false)
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
canDesign,
|
|
||||||
commitContent,
|
|
||||||
content,
|
|
||||||
getConfig,
|
|
||||||
isSaving,
|
|
||||||
onSave,
|
|
||||||
pageKey,
|
|
||||||
selectedLanguage,
|
|
||||||
setSelectedBlockId,
|
|
||||||
translate,
|
|
||||||
])
|
|
||||||
|
|
||||||
// Tasarim modu kisayollari: Ctrl+S kaydet, Ctrl+Z geri al, Ctrl+Shift+Z ileri al, Esc secimi birak.
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isDesignMode) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
|
||||||
const isEditingText =
|
|
||||||
event.target instanceof HTMLElement &&
|
|
||||||
['INPUT', 'TEXTAREA', 'SELECT'].includes(event.target.tagName)
|
|
||||||
|
|
||||||
const key = event.key.toLowerCase()
|
|
||||||
|
|
||||||
if ((event.ctrlKey || event.metaKey) && key === 's') {
|
|
||||||
event.preventDefault()
|
|
||||||
void save()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((event.ctrlKey || event.metaKey) && key === 'z') {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
if (event.shiftKey) {
|
|
||||||
redo()
|
|
||||||
} else {
|
|
||||||
undo()
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === 'Escape' && !isEditingText) {
|
|
||||||
setSelectedBlockId(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown)
|
|
||||||
|
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
||||||
}, [isDesignMode, redo, save, setSelectedBlockId, undo])
|
|
||||||
|
|
||||||
return {
|
|
||||||
...designer,
|
|
||||||
isSaving,
|
|
||||||
isPanelVisible,
|
|
||||||
setIsPanelVisible,
|
|
||||||
selectBlock,
|
|
||||||
changeLanguage,
|
|
||||||
changeField,
|
|
||||||
save,
|
|
||||||
translate,
|
|
||||||
drawerLanguages: resolveDrawerLanguages(languageOptions, designer.supportedLanguages),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13
ui/src/views/public/localization.ts
Normal file
13
ui/src/views/public/localization.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Dil anahtari tanimli degilse okunabilir bir yedek metin dondurur; ceviri bulunamadiginda
|
||||||
|
* ekranda ham anahtarin gorunmesini engeller.
|
||||||
|
*/
|
||||||
|
export function translateLabel(
|
||||||
|
translate: (key: string) => string,
|
||||||
|
key: string,
|
||||||
|
fallback?: string,
|
||||||
|
) {
|
||||||
|
const value = translate('::' + key)
|
||||||
|
|
||||||
|
return value === key ? (fallback ?? key) : value
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/** Public sayfalarda kart ikonlarinin donusumlu renk paleti. */
|
|
||||||
const ICON_COLOR_CLASSES = [
|
|
||||||
'text-blue-600',
|
|
||||||
'text-red-600',
|
|
||||||
'text-green-600',
|
|
||||||
'text-purple-600',
|
|
||||||
'text-yellow-600',
|
|
||||||
'text-indigo-600',
|
|
||||||
]
|
|
||||||
|
|
||||||
export function getIconColor(index: number) {
|
|
||||||
return ICON_COLOR_CLASSES[index % ICON_COLOR_CLASSES.length]
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Tasarim paneli acikken icerigin panel altinda kalmamasi icin sag bosluk sinifi. */
|
|
||||||
export function designerPageClass(isDesignMode: boolean, isPanelVisible: boolean) {
|
|
||||||
return `min-h-screen bg-gray-50 dark:bg-gray-950 ${
|
|
||||||
isDesignMode && isPanelVisible ? 'xl:pr-[420px]' : ''
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,18 @@ const safeListFile = 'safelist.txt'
|
||||||
const SAFELIST_COLORS = 'colors'
|
const SAFELIST_COLORS = 'colors'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}', './safelist.txt'],
|
content: [
|
||||||
|
'./index.html',
|
||||||
|
'./src/**/*.{js,ts,jsx,tsx}',
|
||||||
|
'./safelist.txt',
|
||||||
|
// Custom Component'lerin kodu veritabaninda yasar ve derlemede taranmaz; kullandiklari
|
||||||
|
// siniflar yalnizca seed dosyasinda gorunur. Dosya tarandigi icin public sayfalarin
|
||||||
|
// Tailwind siniflari (gradient, opacity, /alpha tonlari) CSS'e giriyor.
|
||||||
|
// Ayni siniflar `safelist.txt` dosyasina da yazilir (twSafelistGenerator): derleme baglami depo
|
||||||
|
// kokunu gormedigin de — docker, ayri checkout — bu glob bos doner, safelist ise `ui/` icinde
|
||||||
|
// oldugu icin her ortamda taranir.
|
||||||
|
'../configs/seeds/*/data/App.DeveloperKit.CustomComponents.json',
|
||||||
|
],
|
||||||
safelist: [
|
safelist: [
|
||||||
{
|
{
|
||||||
// Covers most utility groups used by designer-entered class strings.
|
// Covers most utility groups used by designer-entered class strings.
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,112 @@
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
const nodePath = require('path')
|
||||||
const plugin = require('tailwindcss/plugin')
|
const plugin = require('tailwindcss/plugin')
|
||||||
const generator = require('./generator')
|
const generator = require('./generator')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
|
|
||||||
module.exports = plugin.withOptions(({ path = 'safelist.txt', patterns = [] }) => ({ theme }) => {
|
/**
|
||||||
|
* Uretilen renk/olcu listesiyle Custom Component siniflarini ayiran isaret. Bilesen siniflari
|
||||||
|
* dosyanin sonunda yasar; kaynak seed dosyasi bulunamadiginda onceki liste oldugu gibi korunur.
|
||||||
|
*/
|
||||||
|
const COMPONENT_MARKER = '# --- custom component classes ---'
|
||||||
|
|
||||||
const safeList = generator(theme)(patterns).join('\n')
|
/** Seed kokunun `ui/` klasorune gore konumu; calisma dizininden bagimsiz cozulur. */
|
||||||
const currentSafeList = fs.readFileSync(path).toString()
|
const SEEDS_ROOT = nodePath.resolve(__dirname, '../../configs/seeds')
|
||||||
|
|
||||||
const hash = crypto.createHash('md5').update(JSON.stringify(safeList)).digest('hex')
|
const addTokens = (value, classes) =>
|
||||||
const prevHash = crypto.createHash('md5').update(JSON.stringify(currentSafeList)).digest('hex')
|
String(value)
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.forEach((token) => classes.add(token))
|
||||||
|
|
||||||
if (hash !== prevHash) {
|
const collectFromNodes = (nodes, classes) => {
|
||||||
return fs.writeFileSync(path, safeList)
|
if (!Array.isArray(nodes)) return
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
for (const [name, value] of Object.entries((node && node.props) || {})) {
|
||||||
|
if (typeof value === 'string' && /class(Name)?$/i.test(name)) addTokens(value, classes)
|
||||||
|
}
|
||||||
|
collectFromNodes(node && node.children, classes)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const collectFromRow = (row, classes) => {
|
||||||
|
try {
|
||||||
|
const props = row && row.Props ? JSON.parse(row.Props) : null
|
||||||
|
collectFromNodes(props && props.visualDesigner && props.visualDesigner.nodes, classes)
|
||||||
|
} catch {
|
||||||
|
// Tasarimci belgesi olmayan (elle yazilmis) bilesen; siniflari asagida kodundan gelir.
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = String((row && row.Code) || '')
|
||||||
|
const pattern = /className=\{?["'`]([^"'`]+)["'`]/g
|
||||||
|
let match = pattern.exec(code)
|
||||||
|
while (match) {
|
||||||
|
addTokens(match[1], classes)
|
||||||
|
match = pattern.exec(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Component'lerin kullandigi siniflar. Bilesen kodu veritabaninda yasar; derlemede
|
||||||
|
* taranabilen tek kaynak seed dosyasidir ve o dosya `ui/` disindadir. Derleme baglami depo kokunu
|
||||||
|
* gormedigin de (docker, ayri checkout) seed'e ozgu siniflar CSS'e hic girmez ve uygulama sunucuda
|
||||||
|
* stilsiz kalir. Siniflar safelist'e yazilir; dosya depoya islendigi icin her ortamda taranir.
|
||||||
|
*/
|
||||||
|
const collectComponentClasses = () => {
|
||||||
|
if (!fs.existsSync(SEEDS_ROOT)) return []
|
||||||
|
|
||||||
|
const files = fs
|
||||||
|
.readdirSync(SEEDS_ROOT, { withFileTypes: true })
|
||||||
|
.filter((entry) => entry.isDirectory())
|
||||||
|
.map((entry) =>
|
||||||
|
nodePath.join(SEEDS_ROOT, entry.name, 'data/App.DeveloperKit.CustomComponents.json'),
|
||||||
|
)
|
||||||
|
.filter((file) => fs.existsSync(file))
|
||||||
|
|
||||||
|
const classes = new Set()
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const seed = JSON.parse(fs.readFileSync(file, 'utf8'))
|
||||||
|
for (const row of seed.Rows || []) collectFromRow(row, classes)
|
||||||
|
} catch {
|
||||||
|
// Bozuk bir seed dosyasi derlemeyi durdurmaz; diger kapsamlar islenir.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...classes].sort()
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = plugin.withOptions(
|
||||||
|
({ path = 'safelist.txt', patterns = [] }) =>
|
||||||
|
({ theme }) => {
|
||||||
|
// Yol calisma dizinine gore cozulurse depo kokunden calistirilan her arac ikinci bir
|
||||||
|
// safelist dosyasi yaratir; eklentinin konumuna sabitlenir, tek dosya kalir.
|
||||||
|
const safeListPath = nodePath.isAbsolute(path)
|
||||||
|
? path
|
||||||
|
: nodePath.resolve(__dirname, '..', path)
|
||||||
|
const currentSafeList = fs.existsSync(safeListPath)
|
||||||
|
? fs.readFileSync(safeListPath).toString()
|
||||||
|
: ''
|
||||||
|
const previousComponents = currentSafeList.split(COMPONENT_MARKER)[1] || ''
|
||||||
|
|
||||||
|
const seedClasses = collectComponentClasses()
|
||||||
|
const components = seedClasses.length
|
||||||
|
? seedClasses
|
||||||
|
: previousComponents.split('\n').filter(Boolean)
|
||||||
|
|
||||||
|
const safeList = [...generator(theme)(patterns), COMPONENT_MARKER, ...components].join(
|
||||||
|
'\n',
|
||||||
|
)
|
||||||
|
|
||||||
|
const hash = crypto.createHash('md5').update(JSON.stringify(safeList)).digest('hex')
|
||||||
|
const prevHash = crypto
|
||||||
|
.createHash('md5')
|
||||||
|
.update(JSON.stringify(currentSafeList))
|
||||||
|
.digest('hex')
|
||||||
|
|
||||||
|
if (hash !== prevHash) {
|
||||||
|
return fs.writeFileSync(safeListPath, safeList)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue