feat: replace raw JSON editor with Shopify-style typed settings editors
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m7s

- Create PageSettingsModels.cs: typed DTOs for landing, about, contact, licenses
- LandingSettingsEditor: accordion sections for hero buttons, trust badges, steps,
  features, stats, testimonials, FAQs, CTA — all with add/remove list management
- AboutSettingsEditor: vision/mission text + icon fields + section titles
- ContactSettingsEditor: address, phone, email, working hours + social media links
- LicensesSettingsEditor: page description + display style selector
- PageSettingsEditDialog: auto-switch editor by PageKey, JSON↔Model serialize/deserialize
- Fallback to raw JSON textarea for unknown page keys
This commit is contained in:
masoodafar-web
2026-02-17 23:50:34 +03:30
parent 3409133757
commit 6147499f9c
7 changed files with 704 additions and 6 deletions
@@ -0,0 +1,66 @@
@using BackOffice.Pages.Content.Components.SettingsModels
<MudExpansionPanels MultiExpansion="true" Elevation="0">
@* ── Vision ── *@
<MudExpansionPanel Text="چشم‌انداز" Icon="@Icons.Material.Filled.Visibility" IsInitiallyExpanded="true">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.VisionTitle"
Label="عنوان"
Placeholder="چشم‌انداز"
Variant="Variant.Outlined" />
<MudTextField @bind-Value="Model.VisionText"
Label="متن چشم‌انداز"
Variant="Variant.Outlined"
Lines="3" />
<MudTextField @bind-Value="Model.VisionIcon"
Label="نام آیکون"
Placeholder="Visibility"
Variant="Variant.Outlined"
HelperText="نام آیکون از مجموعه Material Icons" />
</MudStack>
</MudExpansionPanel>
@* ── Mission ── *@
<MudExpansionPanel Text="مأموریت" Icon="@Icons.Material.Filled.TrackChanges" IsInitiallyExpanded="true">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.MissionTitle"
Label="عنوان"
Placeholder="مأموریت"
Variant="Variant.Outlined" />
<MudTextField @bind-Value="Model.MissionText"
Label="متن مأموریت"
Variant="Variant.Outlined"
Lines="3" />
<MudTextField @bind-Value="Model.MissionIcon"
Label="نام آیکون"
Placeholder="TrackChanges"
Variant="Variant.Outlined"
HelperText="نام آیکون از مجموعه Material Icons" />
</MudStack>
</MudExpansionPanel>
@* ── Section Titles ── *@
<MudExpansionPanel Text="عناوین بخش‌ها" Icon="@Icons.Material.Filled.Title">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.ValuesTitle"
Label="عنوان بخش ارزش‌ها"
Placeholder="ارزش‌های ما"
Variant="Variant.Outlined" />
<MudTextField @bind-Value="Model.TeamTitle"
Label="عنوان بخش تیم"
Placeholder="تیم ما"
Variant="Variant.Outlined" />
</MudStack>
</MudExpansionPanel>
@* ── Hint ── *@
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mt-2">
آیتم‌های ارزش‌ها و اعضای تیم از بخش «مدیریت تصاویر» قابل ویرایش هستند.
</MudAlert>
</MudExpansionPanels>
@code {
[Parameter] public AboutSettingsModel Model { get; set; } = new();
}
@@ -0,0 +1,68 @@
@using BackOffice.Pages.Content.Components.SettingsModels
<MudExpansionPanels MultiExpansion="true" Elevation="0">
@* ── Contact Info ── *@
<MudExpansionPanel Text="اطلاعات تماس" Icon="@Icons.Material.Filled.ContactPhone" IsInitiallyExpanded="true">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.Address"
Label="آدرس"
Variant="Variant.Outlined"
Lines="2"
Placeholder="کرج مهرویلا میدان مادر..." />
<MudGrid Spacing="2">
<MudItem xs="12" md="6">
<MudTextField @bind-Value="Model.Phone"
Label="شماره تلفن"
Placeholder="026-34233563"
Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" md="6">
<MudTextField @bind-Value="Model.Email"
Label="ایمیل"
Placeholder="info@kbs1.co"
Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
<MudTextField @bind-Value="Model.WorkingHours"
Label="ساعات کاری"
Placeholder="شنبه تا پنج‌شنبه — ۹ صبح تا ۶ عصر"
Variant="Variant.Outlined" />
</MudStack>
</MudExpansionPanel>
@* ── Social Media ── *@
<MudExpansionPanel Text="شبکه‌های اجتماعی" Icon="@Icons.Material.Filled.Share" IsInitiallyExpanded="true">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.TelegramUrl"
Label="تلگرام"
Placeholder="https://t.me/kbs1"
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Custom.Brands.Telegram" />
<MudTextField @bind-Value="Model.InstagramUrl"
Label="اینستاگرام"
Placeholder="https://instagram.com/kbs1"
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Custom.Brands.Instagram" />
<MudTextField @bind-Value="Model.LinkedinUrl"
Label="لینکدین"
Placeholder="https://linkedin.com/company/kbs1"
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Custom.Brands.LinkedIn" />
<MudTextField @bind-Value="Model.WhatsappUrl"
Label="واتساپ"
Placeholder="https://wa.me/989123456789"
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Custom.Brands.WhatsApp" />
</MudStack>
</MudExpansionPanel>
</MudExpansionPanels>
@code {
[Parameter] public ContactSettingsModel Model { get; set; } = new();
}
@@ -0,0 +1,332 @@
@using BackOffice.Pages.Content.Components.SettingsModels
<MudExpansionPanels MultiExpansion="true" Elevation="0">
@* ── Hero Buttons ── *@
<MudExpansionPanel Text="دکمه‌های هیرو" Icon="@Icons.Material.Filled.TouchApp" IsInitiallyExpanded="true">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.HeroButtonPrimaryText"
Label="متن دکمه اصلی"
Placeholder="شروع رایگان"
Variant="Variant.Outlined" />
<MudTextField @bind-Value="Model.HeroButtonSecondaryText"
Label="متن دکمه ثانویه"
Placeholder="آخرین اخبار"
Variant="Variant.Outlined" />
</MudStack>
</MudExpansionPanel>
@* ── Trust Badges ── *@
<MudExpansionPanel Text="@($"نشان‌های اعتماد ({Model.TrustBadges.Count})")" Icon="@Icons.Material.Filled.Verified">
<MudStack Spacing="2" Class="pa-2">
@for (var i = 0; i < Model.TrustBadges.Count; i++)
{
var index = i;
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="min-width:20px;">@(index + 1)</MudText>
<MudTextField @bind-Value="Model.TrustBadges[index].IconName"
Label="نام آیکون"
Placeholder="Timer"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:1;" />
<MudTextField @bind-Value="Model.TrustBadges[index].Text"
Label="متن"
Placeholder="ثبت‌نام زیر ۲ دقیقه"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:2;" />
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
Size="Size.Small"
OnClick="() => Model.TrustBadges.RemoveAt(index)" />
</MudStack>
</MudPaper>
}
<MudButton StartIcon="@Icons.Material.Filled.Add"
Color="Color.Primary"
Variant="Variant.Text"
OnClick="() => Model.TrustBadges.Add(new TrustBadgeModel())">
افزودن نشان
</MudButton>
</MudStack>
</MudExpansionPanel>
@* ── Steps ── *@
<MudExpansionPanel Text="@($"مراحل ({Model.Steps.Count})")" Icon="@Icons.Material.Filled.FormatListNumbered">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.StepsTitle"
Label="عنوان بخش مراحل"
Placeholder="سه گام تا شروع"
Variant="Variant.Outlined" />
@for (var i = 0; i < Model.Steps.Count; i++)
{
var index = i;
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
<MudStack Spacing="2">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudAvatar Size="Size.Small" Color="Color.Primary">
<MudText Typo="Typo.caption" Style="color:#fff;">@(index + 1)</MudText>
</MudAvatar>
<MudTextField @bind-Value="Model.Steps[index].Title"
Label="عنوان مرحله"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:1;" />
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
Size="Size.Small"
OnClick="() => Model.Steps.RemoveAt(index)" />
</MudStack>
<MudTextField @bind-Value="Model.Steps[index].Description"
Label="توضیحات"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Lines="2" />
</MudStack>
</MudPaper>
}
<MudButton StartIcon="@Icons.Material.Filled.Add"
Color="Color.Primary"
Variant="Variant.Text"
OnClick="() => Model.Steps.Add(new StepModel())">
افزودن مرحله
</MudButton>
</MudStack>
</MudExpansionPanel>
@* ── Features ── *@
<MudExpansionPanel Text="@($"ویژگی‌ها ({Model.Features.Count})")" Icon="@Icons.Material.Filled.Star">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.FeaturesTitle"
Label="عنوان بخش ویژگی‌ها"
Placeholder="چرا کارا بازار سلامت؟"
Variant="Variant.Outlined" />
@for (var i = 0; i < Model.Features.Count; i++)
{
var index = i;
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
<MudStack Spacing="2">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="min-width:20px;">@(index + 1)</MudText>
<MudTextField @bind-Value="Model.Features[index].IconName"
Label="نام آیکون"
Placeholder="VerifiedUser"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:1;" />
<MudTextField @bind-Value="Model.Features[index].Title"
Label="عنوان"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:2;" />
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
Size="Size.Small"
OnClick="() => Model.Features.RemoveAt(index)" />
</MudStack>
<MudTextField @bind-Value="Model.Features[index].Description"
Label="توضیحات"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Lines="2" />
</MudStack>
</MudPaper>
}
<MudButton StartIcon="@Icons.Material.Filled.Add"
Color="Color.Primary"
Variant="Variant.Text"
OnClick="() => Model.Features.Add(new FeatureModel())">
افزودن ویژگی
</MudButton>
</MudStack>
</MudExpansionPanel>
@* ── Stats ── *@
<MudExpansionPanel Text="@($"آمار ({Model.Stats.Count})")" Icon="@Icons.Material.Filled.BarChart">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.StatsTitle"
Label="عنوان بخش آمار"
Placeholder="آمار پلتفرم"
Variant="Variant.Outlined" />
@for (var i = 0; i < Model.Stats.Count; i++)
{
var index = i;
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="min-width:20px;">@(index + 1)</MudText>
<MudTextField @bind-Value="Model.Stats[index].Label"
Label="عنوان"
Placeholder="رشد میانگین تیم"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:2;" />
<MudNumericField @bind-Value="Model.Stats[index].Value"
Label="مقدار"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:1;" />
<MudTextField @bind-Value="Model.Stats[index].Suffix"
Label="پسوند"
Placeholder="%+"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:1;" />
<MudSelect @bind-Value="Model.Stats[index].Color"
Label="رنگ"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:1;">
<MudSelectItem Value="@("Primary")">آبی</MudSelectItem>
<MudSelectItem Value="@("Secondary")">بنفش</MudSelectItem>
<MudSelectItem Value="@("Success")">سبز</MudSelectItem>
<MudSelectItem Value="@("Warning")">زرد</MudSelectItem>
<MudSelectItem Value="@("Error")">قرمز</MudSelectItem>
<MudSelectItem Value="@("Info")">آبی روشن</MudSelectItem>
</MudSelect>
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
Size="Size.Small"
OnClick="() => Model.Stats.RemoveAt(index)" />
</MudStack>
</MudPaper>
}
<MudButton StartIcon="@Icons.Material.Filled.Add"
Color="Color.Primary"
Variant="Variant.Text"
OnClick="() => Model.Stats.Add(new StatModel())">
افزودن آمار
</MudButton>
</MudStack>
</MudExpansionPanel>
@* ── Testimonials ── *@
<MudExpansionPanel Text="@($"نظرات مشتریان ({Model.Testimonials.Count})")" Icon="@Icons.Material.Filled.FormatQuote">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.TestimonialsTitle"
Label="عنوان بخش نظرات"
Placeholder="اعتماد مشتریان"
Variant="Variant.Outlined" />
@for (var i = 0; i < Model.Testimonials.Count; i++)
{
var index = i;
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
<MudStack Spacing="2">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="min-width:20px;">@(index + 1)</MudText>
<MudTextField @bind-Value="Model.Testimonials[index].Name"
Label="نام"
Placeholder="شرکت سینا نت"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:1;" />
<MudTextField @bind-Value="Model.Testimonials[index].Role"
Label="سمت"
Placeholder="مدیر عملیات"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:1;" />
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
Size="Size.Small"
OnClick="() => Model.Testimonials.RemoveAt(index)" />
</MudStack>
<MudTextField @bind-Value="Model.Testimonials[index].Quote"
Label="متن نظر"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Lines="2" />
</MudStack>
</MudPaper>
}
<MudButton StartIcon="@Icons.Material.Filled.Add"
Color="Color.Primary"
Variant="Variant.Text"
OnClick="() => Model.Testimonials.Add(new TestimonialModel())">
افزودن نظر
</MudButton>
</MudStack>
</MudExpansionPanel>
@* ── FAQs ── *@
<MudExpansionPanel Text="@($"سوالات متداول ({Model.Faqs.Count})")" Icon="@Icons.Material.Filled.QuestionAnswer">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.FaqTitle"
Label="عنوان بخش سوالات"
Placeholder="سوالات متداول"
Variant="Variant.Outlined" />
@for (var i = 0; i < Model.Faqs.Count; i++)
{
var index = i;
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Style="border:1px solid var(--mud-palette-lines-default);">
<MudStack Spacing="2">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="min-width:20px;">@(index + 1)</MudText>
<MudTextField @bind-Value="Model.Faqs[index].Question"
Label="سوال"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Style="flex:1;" />
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
Size="Size.Small"
OnClick="() => Model.Faqs.RemoveAt(index)" />
</MudStack>
<MudTextField @bind-Value="Model.Faqs[index].Answer"
Label="پاسخ"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Lines="2" />
</MudStack>
</MudPaper>
}
<MudButton StartIcon="@Icons.Material.Filled.Add"
Color="Color.Primary"
Variant="Variant.Text"
OnClick="() => Model.Faqs.Add(new FaqModel())">
افزودن سوال
</MudButton>
</MudStack>
</MudExpansionPanel>
@* ── CTA Section ── *@
<MudExpansionPanel Text="بنر فراخوان (CTA)" Icon="@Icons.Material.Filled.Campaign">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.CtaTitle"
Label="عنوان CTA"
Placeholder="آماده شروع هستید؟"
Variant="Variant.Outlined" />
<MudTextField @bind-Value="Model.CtaDescription"
Label="توضیحات CTA"
Placeholder="همین الان ثبت‌نام کنید..."
Variant="Variant.Outlined"
Lines="2" />
<MudTextField @bind-Value="Model.CtaButtonText"
Label="متن دکمه CTA"
Placeholder="شروع رایگان"
Variant="Variant.Outlined" />
</MudStack>
</MudExpansionPanel>
@* ── Blog Section ── *@
<MudExpansionPanel Text="بخش بلاگ" Icon="@Icons.Material.Filled.Article">
<MudStack Spacing="2" Class="pa-2">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudSwitch T="bool" @bind-Value="_featuredBlogEnabled" Color="Color.Primary" />
<MudText Typo="Typo.body2">نمایش آخرین مطالب بلاگ</MudText>
</MudStack>
</MudStack>
</MudExpansionPanel>
</MudExpansionPanels>
@code {
[Parameter] public LandingSettingsModel Model { get; set; } = new();
private bool _featuredBlogEnabled
{
get => Model.FeaturedBlogEnabled ?? true;
set => Model.FeaturedBlogEnabled = value;
}
}
@@ -0,0 +1,32 @@
@using BackOffice.Pages.Content.Components.SettingsModels
<MudExpansionPanels MultiExpansion="true" Elevation="0">
@* ── Page Settings ── *@
<MudExpansionPanel Text="تنظیمات صفحه" Icon="@Icons.Material.Filled.Settings" IsInitiallyExpanded="true">
<MudStack Spacing="2" Class="pa-2">
<MudTextField @bind-Value="Model.PageDescription"
Label="توضیحات صفحه"
Variant="Variant.Outlined"
Lines="3"
Placeholder="مجوزها و نمادهای اعتماد الکترونیکی ما..." />
<MudSelect @bind-Value="Model.DisplayStyle"
Label="نوع نمایش"
Variant="Variant.Outlined">
<MudSelectItem Value="@("grid")">گرید (Grid)</MudSelectItem>
<MudSelectItem Value="@("list")">لیست (List)</MudSelectItem>
<MudSelectItem Value="@("carousel")">اسلایدر (Carousel)</MudSelectItem>
</MudSelect>
</MudStack>
</MudExpansionPanel>
@* ── Hint ── *@
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mt-2">
تصاویر مجوزها از بخش «مدیریت تصاویر» قابل افزودن و ویرایش هستند.
</MudAlert>
</MudExpansionPanels>
@code {
[Parameter] public LicensesSettingsModel Model { get; set; } = new();
}
@@ -1,4 +1,5 @@
@using BackOffice.Services.Content
@using BackOffice.Pages.Content.Components.SettingsModels
@using Microsoft.AspNetCore.Components.Forms
@using BackOffice.Common.BaseComponents
@@ -63,12 +64,34 @@
<MudDivider Class="my-2" />
<MudText Typo="Typo.subtitle2">تنظیمات اختصاصی (JSON)</MudText>
<MudTextField @bind-Value="Model.SettingsJson"
Label="تنظیمات JSON"
Variant="Variant.Outlined"
Lines="8"
HelperText="تنظیمات اختصاصی هر صفحه — مثلاً خدمات، مراحل، FAQ و..." />
<MudText Typo="Typo.subtitle1" Class="mb-1">
<MudIcon Icon="@Icons.Material.Filled.Tune" Size="Size.Small" Class="ml-1" />
تنظیمات اختصاصی «@PageTitle»
</MudText>
@* ── Typed Settings Editors ── *@
@switch (Model.PageKey)
{
case "landing":
<LandingSettingsEditor Model="_landingSettings" />
break;
case "about":
<AboutSettingsEditor Model="_aboutSettings" />
break;
case "contact":
<ContactSettingsEditor Model="_contactSettings" />
break;
case "licenses":
<LicensesSettingsEditor Model="_licensesSettings" />
break;
default:
<MudTextField @bind-Value="Model.SettingsJson"
Label="تنظیمات JSON"
Variant="Variant.Outlined"
Lines="8"
HelperText="تنظیمات اختصاصی صفحه (JSON)" />
break;
}
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudSwitch T="bool" @bind-Value="Model.IsActive" Color="Color.Primary" />
@@ -1,3 +1,5 @@
using System.Text.Json;
using BackOffice.Pages.Content.Components.SettingsModels;
using BackOffice.Services.Content;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
@@ -23,6 +25,70 @@ public partial class PageSettingsEditDialog
private string? _imagePreview;
private readonly long _maxAllowedSize = (1024 * 1024) * 5;
// ── Typed settings models ──
private LandingSettingsModel _landingSettings = new();
private AboutSettingsModel _aboutSettings = new();
private ContactSettingsModel _contactSettings = new();
private LicensesSettingsModel _licensesSettings = new();
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
protected override void OnParametersSet()
{
DeserializeSettings();
}
/// <summary>
/// Parse existing SettingsJson into the typed model based on PageKey
/// </summary>
private void DeserializeSettings()
{
if (string.IsNullOrWhiteSpace(Model.SettingsJson)) return;
try
{
switch (Model.PageKey)
{
case "landing":
_landingSettings = JsonSerializer.Deserialize<LandingSettingsModel>(Model.SettingsJson, JsonOptions) ?? new();
break;
case "about":
_aboutSettings = JsonSerializer.Deserialize<AboutSettingsModel>(Model.SettingsJson, JsonOptions) ?? new();
break;
case "contact":
_contactSettings = JsonSerializer.Deserialize<ContactSettingsModel>(Model.SettingsJson, JsonOptions) ?? new();
break;
case "licenses":
_licensesSettings = JsonSerializer.Deserialize<LicensesSettingsModel>(Model.SettingsJson, JsonOptions) ?? new();
break;
}
}
catch
{
// If JSON is malformed, start with empty model
}
}
/// <summary>
/// Serialize the typed model back to JSON before saving
/// </summary>
private void SerializeSettings()
{
Model.SettingsJson = Model.PageKey switch
{
"landing" => JsonSerializer.Serialize(_landingSettings, JsonOptions),
"about" => JsonSerializer.Serialize(_aboutSettings, JsonOptions),
"contact" => JsonSerializer.Serialize(_contactSettings, JsonOptions),
"licenses" => JsonSerializer.Serialize(_licensesSettings, JsonOptions),
_ => Model.SettingsJson // keep raw JSON for unknown pages
};
}
private async Task OnImageSelected(IBrowserFile? file)
{
_imageFile = file;
@@ -46,6 +112,9 @@ public partial class PageSettingsEditDialog
_loading = true;
// Serialize typed model → JSON before saving
SerializeSettings();
if (_imageFile != null && _imageBuffer != null)
{
Model.ImageFile = _imageBuffer;
@@ -0,0 +1,108 @@
namespace BackOffice.Pages.Content.Components.SettingsModels;
// ══════════════════════════════════════════════════════════════
// LANDING PAGE
// ══════════════════════════════════════════════════════════════
public class LandingSettingsModel
{
public string? HeroButtonPrimaryText { get; set; }
public string? HeroButtonSecondaryText { get; set; }
public List<TrustBadgeModel> TrustBadges { get; set; } = new();
public string? StepsTitle { get; set; }
public List<StepModel> Steps { get; set; } = new();
public string? FeaturesTitle { get; set; }
public List<FeatureModel> Features { get; set; } = new();
public string? StatsTitle { get; set; }
public List<StatModel> Stats { get; set; } = new();
public string? TestimonialsTitle { get; set; }
public List<TestimonialModel> Testimonials { get; set; } = new();
public string? FaqTitle { get; set; }
public List<FaqModel> Faqs { get; set; } = new();
public string? CtaTitle { get; set; }
public string? CtaDescription { get; set; }
public string? CtaButtonText { get; set; }
public bool? FeaturedBlogEnabled { get; set; }
}
public class TrustBadgeModel
{
public string? IconName { get; set; }
public string? Text { get; set; }
}
public class StepModel
{
public string? Title { get; set; }
public string? Description { get; set; }
}
public class FeatureModel
{
public string? IconName { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
}
public class StatModel
{
public string? Label { get; set; }
public double Value { get; set; }
public string? Suffix { get; set; }
public string? Color { get; set; }
}
public class TestimonialModel
{
public string? Quote { get; set; }
public string? Name { get; set; }
public string? Role { get; set; }
}
public class FaqModel
{
public string? Question { get; set; }
public string? Answer { get; set; }
}
// ══════════════════════════════════════════════════════════════
// ABOUT PAGE
// ══════════════════════════════════════════════════════════════
public class AboutSettingsModel
{
public string? VisionTitle { get; set; }
public string? VisionText { get; set; }
public string? VisionIcon { get; set; }
public string? MissionTitle { get; set; }
public string? MissionText { get; set; }
public string? MissionIcon { get; set; }
public string? ValuesTitle { get; set; }
public string? TeamTitle { get; set; }
}
// ══════════════════════════════════════════════════════════════
// CONTACT PAGE
// ══════════════════════════════════════════════════════════════
public class ContactSettingsModel
{
public string? Address { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? WorkingHours { get; set; }
public string? TelegramUrl { get; set; }
public string? InstagramUrl { get; set; }
public string? LinkedinUrl { get; set; }
public string? WhatsappUrl { get; set; }
}
// ══════════════════════════════════════════════════════════════
// LICENSES PAGE
// ══════════════════════════════════════════════════════════════
public class LicensesSettingsModel
{
public string? PageDescription { get; set; }
public string? DisplayStyle { get; set; }
}