refactor: migrate Landing page to SitePageSettings system
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 6m33s

- Index.razor.cs: load landing SettingsJson via SitePageSettingsService
- PopulateFromSettings(): parse trustBadges, steps, features, stats, testimonials, faqs from DB
- ResolveIcon(): dynamically resolve MudBlazor icons from icon name strings
- Index.razor: make hero text, section titles, CTA dynamic with fallback defaults
- All hardcoded data preserved as fallbacks when DB is unavailable
This commit is contained in:
masoodafar-web
2026-02-17 23:17:09 +03:30
parent 997ab80fa4
commit be78bc683a
2 changed files with 219 additions and 68 deletions
+11 -11
View File
@@ -15,11 +15,11 @@
</MudChip>
<MudText Typo="Typo.h1" Class="hero-title" Style="max-width:680px;">
رشد تیم، فروش واقعی، پاداش شفاف
@(_pageData?.HeroTitle ?? "رشد تیم، فروش واقعی، پاداش شفاف")
</MudText>
<MudText Typo="Typo.body1" Class="hero-subtitle" Style="max-width:520px;">
با دعوت از دوستان، از خریدهای واقعی محصولات سلامت پاداش بگیرید. ثبت‌نام سریع، داشبورد لحظه‌ای.
@(_pageData?.HeroSubtitle ?? "با دعوت از دوستان، از خریدهای واقعی محصولات سلامت پاداش بگیرید. ثبت‌نام سریع، داشبورد لحظه‌ای.")
</MudText>
<MudStack Row="true" Spacing="2" Class="mt-2 flex-wrap" Justify="Justify.Center">
@@ -27,13 +27,13 @@
Class="rounded-pill hero-cta-primary"
Size="Size.Large"
OnClick="NavigateToRegistrationWizard">
شروع رایگان
@(_settings?.HeroButtonPrimaryText ?? "شروع رایگان")
</MudButton>
<MudButton Variant="Variant.Outlined"
Class="rounded-pill hero-cta-outline"
Size="Size.Large"
OnClick="@(() => Navigation.NavigateTo("/blog"))">
آخرین اخبار
@(_settings?.HeroButtonSecondaryText ?? "آخرین اخبار")
</MudButton>
</MudStack>
@@ -99,7 +99,7 @@
<section class="section-landing">
<MudContainer MaxWidth="MaxWidth.Large">
<div class="text-center mb-8 fade-in-up">
<MudText Typo="Typo.h3">سه گام تا شروع</MudText>
<MudText Typo="Typo.h3">@(_settings?.StepsTitle ?? "سه گام تا شروع")</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
بدون پیچیدگی، سریع و آسان
</MudText>
@@ -128,7 +128,7 @@
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
<MudContainer MaxWidth="MaxWidth.Large">
<div class="text-center mb-8 fade-in-up">
<MudText Typo="Typo.h3">چرا کارا بازار سلامت؟</MudText>
<MudText Typo="Typo.h3">@(_settings?.FeaturesTitle ?? "چرا کارا بازار سلامت؟")</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2" Style="max-width:540px;margin:0 auto;">
ابزارهایی ساده و قدرتمند برای رشد کسب‌وکار شما
</MudText>
@@ -259,7 +259,7 @@
<section class="section-landing">
<MudContainer MaxWidth="MaxWidth.Large">
<div class="text-center mb-8 fade-in-up">
<MudText Typo="Typo.h3">اعتماد مشتریان</MudText>
<MudText Typo="Typo.h3">@(_settings?.TestimonialsTitle ?? "اعتماد مشتریان")</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
بخشی از تجربه استفاده از «کارا بازار سلامت»
</MudText>
@@ -299,7 +299,7 @@
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
<MudContainer MaxWidth="MaxWidth.Medium">
<div class="text-center mb-8 fade-in-up">
<MudText Typo="Typo.h3">سوالات متداول</MudText>
<MudText Typo="Typo.h3">@(_settings?.FaqTitle ?? "سوالات متداول")</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
پاسخ به سوالات رایج شما
</MudText>
@@ -326,16 +326,16 @@
<div class="cta-banner pa-8 pa-md-12 fade-in-up">
<MudStack AlignItems="AlignItems.Center" Spacing="3">
<MudText Typo="Typo.h3" Align="Align.Center" Class="dash-hero-name">
آماده شروع هستید؟
@(_settings?.CtaTitle ?? "آماده شروع هستید؟")
</MudText>
<MudText Typo="Typo.body1" Align="Align.Center" Class="hero-subtitle">
همین الان ثبت‌نام کنید و از مزایای کارا بازار سلامت بهره‌مند شوید.
@(_settings?.CtaDescription ?? "همین الان ثبت‌نام کنید و از مزایای کارا بازار سلامت بهره‌مند شوید.")
</MudText>
<MudButton Variant="Variant.Filled"
Size="Size.Large"
Class="rounded-pill mt-2 hero-cta-primary"
OnClick="NavigateToRegistrationWizard">
شروع رایگان
@(_settings?.CtaButtonText ?? "شروع رایگان")
</MudButton>
</MudStack>
</div>
+208 -57
View File
@@ -9,21 +9,48 @@ public partial class Index : IDisposable
{
[Inject] private BlogPostService BlogPostService { get; set; } = default!;
[Inject] private AuthService AuthService { get; set; } = default!;
[Inject] private SitePageSettingsService PageSettingsService { get; set; } = default!;
// ── CMS page data ──
private PageSettingsDto? _pageData;
private LandingSettings? _settings;
// ── Latest blog posts (loaded from CMS) ──
private List<BlogPostCardDto> _latestPosts = new();
// ── Data lists (populated from DB or fallback) ──
private List<(string Icon, string Text)> _trustBadges = new();
private List<(string Title, string Desc)> _steps = new();
private List<(string Icon, string Title, string Desc, int Delay)> _features = new();
private List<StatItem> _stats = new();
private List<TestimonialItem> _testimonials = new();
private List<QA> _faqs = new();
protected override async Task OnInitializedAsync()
{
MainService.OnChangeHandler += OnStateChanged;
// Load landing page settings from CMS
try
{
_pageData = await PageSettingsService.GetPageAsync("landing");
if (_pageData != null)
{
_settings = _pageData.GetSettings<LandingSettings>();
}
}
catch
{
// Fallback: settings remain null → defaults below
}
PopulateFromSettings();
// Load latest published blog posts
// اول پین‌شده‌ها، اگر کافی نبود آخرین پست‌ها اضافه شود
try
{
_latestPosts = await BlogPostService.GetFeaturedPostsAsync(2);
// اگر فقط ۱ پین‌شده داریم، ۱ پست آخر هم اضافه کن
if (_latestPosts.Count < 2)
{
var result = await BlogPostService.GetPublishedPostsAsync(page: 1, pageSize: 2);
@@ -44,6 +71,155 @@ public partial class Index : IDisposable
}
}
private void PopulateFromSettings()
{
// ── Trust badges ──
if (_settings?.TrustBadges?.Any() == true)
{
_trustBadges = _settings.TrustBadges
.Select(b => (ResolveIcon(b.IconName), b.Text ?? ""))
.ToList();
}
else
{
_trustBadges = new()
{
(Icons.Material.Outlined.Timer, "ثبت‌نام زیر ۲ دقیقه"),
(Icons.Material.Outlined.SupportAgent, "پشتیبانی ۷×۲۴"),
(Icons.Material.Outlined.Lock, "پرداخت ایمن"),
(Icons.Material.Outlined.Verified, "تضمین کیفیت"),
};
}
// ── Steps ──
if (_settings?.Steps?.Any() == true)
{
_steps = _settings.Steps
.Select(s => (s.Title ?? "", s.Description ?? ""))
.ToList();
}
else
{
_steps = new()
{
("ثبت‌نام و احراز هویت", "یک حساب بسازید، شماره موبایل را تأیید و اطلاعات هویتی را تکمیل کنید."),
("دعوت دوستان", "لینک دعوت اختصاصی خود را با دوستان و آشنایان به اشتراک بگذارید."),
("دریافت پاداش", "از خریدهای واقعی اعضای تیمتان پاداش شفاف و لحظه‌ای دریافت کنید."),
};
}
// ── Features ──
if (_settings?.Features?.Any() == true)
{
_features = _settings.Features
.Select((f, i) => (ResolveIcon(f.IconName), f.Title ?? "", f.Description ?? "", i * 100))
.ToList();
}
else
{
_features = new()
{
(Icons.Material.Outlined.VerifiedUser, "ثبت‌نام سریع و ساده", "در چند گام کوتاه حساب بسازید و شروع کنید.", 0),
(Icons.Material.Outlined.StarBorder, "پاداش‌های شفاف", "قوانین روشن، دسترسی آسان به سوابق و گزارش‌ها.", 100),
(Icons.Material.Outlined.Devices, "طراحی واکنش‌گرا", "تجربه‌ای روان در موبایل و دسکتاپ.", 200),
(Icons.Material.Outlined.Groups, "تیم‌سازی هوشمند", "ساختار درختی شبکه و مدیریت تیم‌های فروش.", 300),
(Icons.Material.Outlined.Insights, "گزارش‌های لحظه‌ای", "داشبورد پویا برای مشاهده عملکرد و کمیسیون.", 400),
(Icons.Material.Outlined.Lock, "امنیت بالا", "رمزنگاری اطلاعات و احراز هویت چندمرحله‌ای.", 500),
};
}
// ── Stats ──
if (_settings?.Stats?.Any() == true)
{
_stats = _settings.Stats
.Select((s, i) => new StatItem(
$"stat-{i}",
$"{s.Value}{s.Suffix}",
s.Value,
s.Suffix ?? "",
ResolveColor(s.Color),
s.Label ?? ""))
.ToList();
}
else
{
_stats = new()
{
new("stat-growth", "+۵۰٪", 50, "%+", Color.Success, "رشد میانگین تیم"),
new("stat-uptime", "۹۹.۹٪", 99.9, "%", Color.Primary, "آپ‌تایم سرویس"),
new("stat-deploy", "۳ روز", 3, " روز", Color.Warning, "میانگین زمان استقرار"),
new("stat-coverage", "+۲۰ کشور", 20, "+", Color.Secondary, "پوشش ارسال کد"),
};
}
// ── Testimonials ──
if (_settings?.Testimonials?.Any() == true)
{
_testimonials = _settings.Testimonials
.Select((t, i) => new TestimonialItem(t.Quote ?? "", t.Name ?? "", t.Role ?? "", i * 150))
.ToList();
}
else
{
_testimonials = new()
{
new("با کارا بازار سلامت، محاسبه کارمزدها و پایش تیم‌ها بدون اکسل و دردسر انجام می‌شود.", "شرکت سینا نت", "مدیر عملیات", 0),
new("شجره‌نامه بصری و گزارش‌های دقیق باعث شد رشد تیم را لحظه‌ای ببینیم.", "هولدینگ آریانا", "مدیر فروش", 150),
new("سادگی ثبت‌نام و شفافیت پاداش‌ها مهم‌ترین مزیت این پلتفرم است.", "گروه بهداشتی نوین", "مدیر توسعه", 300),
};
}
// ── FAQs ──
if (_settings?.Faqs?.Any() == true)
{
_faqs = _settings.Faqs
.Select(f => new QA(f.Question ?? "", f.Answer ?? ""))
.ToList();
}
else
{
_faqs = new()
{
new("دامنه اختصاصی دارم؛ قابل اتصال است؟", "بله، پشت دامنه و گواهی SSL خودتان مستقر می‌شود."),
new("با دیتابیس خودم کار می‌کند؟", "کاملاً. SQL Server، PostgreSQL و MySQL پشتیبانی می‌شود."),
new("چه درگاه‌هایی پشتیبانی می‌شود؟", "Stripe و PayPal یا درگاه اختصاصی از طریق وب‌هوک‌ها."),
new("می‌توانم داده‌ها را خروجی بگیرم؟", "هر زمان از داشبورد ادمین خروجی CSV/Excel بگیرید."),
};
}
}
/// <summary>
/// Resolve icon name from SettingsJson to MudBlazor icon string.
/// Falls back to a generic icon.
/// </summary>
private static string ResolveIcon(string? iconName)
{
if (string.IsNullOrWhiteSpace(iconName)) return Icons.Material.Outlined.Info;
// Try to get from MudBlazor Icons via reflection
var field = typeof(Icons.Material.Outlined).GetField(iconName,
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (field != null) return (string)(field.GetValue(null) ?? Icons.Material.Outlined.Info);
// Also try Filled
field = typeof(Icons.Material.Filled).GetField(iconName,
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (field != null) return (string)(field.GetValue(null) ?? Icons.Material.Outlined.Info);
return Icons.Material.Outlined.Info;
}
private static Color ResolveColor(string? colorName) => colorName?.ToLowerInvariant() switch
{
"primary" => Color.Primary,
"secondary" => Color.Secondary,
"success" => Color.Success,
"warning" => Color.Warning,
"error" => Color.Error,
"info" => Color.Info,
_ => Color.Default
};
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
@@ -88,65 +264,40 @@ public partial class Index : IDisposable
await InvokeAsync(StateHasChanged);
}
// ── Trust badges ──
private readonly List<(string Icon, string Text)> _trustBadges = new()
{
(Icons.Material.Outlined.Timer, "ثبت‌نام زیر ۲ دقیقه"),
(Icons.Material.Outlined.SupportAgent, "پشتیبانی ۷×۲۴"),
(Icons.Material.Outlined.Lock, "پرداخت ایمن"),
(Icons.Material.Outlined.Verified, "تضمین کیفیت"),
};
// ── How it works steps ──
private readonly List<(string Title, string Desc)> _steps = new()
{
("ثبت‌نام و احراز هویت", "یک حساب بسازید، شماره موبایل را تأیید و اطلاعات هویتی را تکمیل کنید."),
("دعوت دوستان", "لینک دعوت اختصاصی خود را با دوستان و آشنایان به اشتراک بگذارید."),
("دریافت پاداش", "از خریدهای واقعی اعضای تیمتان پاداش شفاف و لحظه‌ای دریافت کنید."),
};
// ── Feature cards data (6 cards) ──
private readonly List<(string Icon, string Title, string Desc, int Delay)> _features = new()
{
(Icons.Material.Outlined.VerifiedUser, "ثبت‌نام سریع و ساده", "در چند گام کوتاه حساب بسازید و شروع کنید.", 0),
(Icons.Material.Outlined.StarBorder, "پاداش‌های شفاف", "قوانین روشن، دسترسی آسان به سوابق و گزارش‌ها.", 100),
(Icons.Material.Outlined.Devices, "طراحی واکنش‌گرا", "تجربه‌ای روان در موبایل و دسکتاپ.", 200),
(Icons.Material.Outlined.Groups, "تیم‌سازی هوشمند", "ساختار درختی شبکه و مدیریت تیم‌های فروش.", 300),
(Icons.Material.Outlined.Insights, "گزارش‌های لحظه‌ای", "داشبورد پویا برای مشاهده عملکرد و کمیسیون.", 400),
(Icons.Material.Outlined.Lock, "امنیت بالا", "رمزنگاری اطلاعات و احراز هویت چندمرحله‌ای.", 500),
};
// ── Stats data (animated counters) ──
private readonly List<StatItem> _stats = new()
{
new("stat-growth", "+۵۰٪", 50, "%+", Color.Success, "رشد میانگین تیم"),
new("stat-uptime", "۹۹.۹٪", 99.9, "%", Color.Primary, "آپ‌تایم سرویس"),
new("stat-deploy", "۳ روز", 3, " روز", Color.Warning, "میانگین زمان استقرار"),
new("stat-coverage", "+۲۰ کشور", 20, "+", Color.Secondary, "پوشش ارسال کد"),
};
// ── Testimonials ──
private readonly List<TestimonialItem> _testimonials = new()
{
new("با کارا بازار سلامت، محاسبه کارمزدها و پایش تیم‌ها بدون اکسل و دردسر انجام می‌شود.", "شرکت سینا نت", "مدیر عملیات", 0),
new("شجره‌نامه بصری و گزارش‌های دقیق باعث شد رشد تیم را لحظه‌ای ببینیم.", "هولدینگ آریانا", "مدیر فروش", 150),
new("سادگی ثبت‌نام و شفافیت پاداش‌ها مهم‌ترین مزیت این پلتفرم است.", "گروه بهداشتی نوین", "مدیر توسعه", 300),
};
// ── FAQ data ──
private readonly List<QA> _faqs = new()
{
new("دامنه اختصاصی دارم؛ قابل اتصال است؟", "بله، پشت دامنه و گواهی SSL خودتان مستقر می‌شود."),
new("با دیتابیس خودم کار می‌کند؟", "کاملاً. SQL Server، PostgreSQL و MySQL پشتیبانی می‌شود."),
new("چه درگاه‌هایی پشتیبانی می‌شود؟", "Stripe و PayPal یا درگاه اختصاصی از طریق وب‌هوک‌ها."),
new("می‌توانم داده‌ها را خروجی بگیرم؟", "هر زمان از داشبورد ادمین خروجی CSV/Excel بگیرید."),
};
// ── Records ──
private record QA(string Q, string A);
private record StatItem(string ElementId, string Display, double Target, string Suffix, Color Color, string Label);
private record TestimonialItem(string Quote, string Name, string Role, int Delay);
// ── SettingsJson DTO ──
private class LandingSettings
{
public string? HeroButtonPrimaryText { get; set; }
public string? HeroButtonSecondaryText { get; set; }
public List<TrustBadgeItem>? TrustBadges { get; set; }
public string? StepsTitle { get; set; }
public List<StepItem>? Steps { get; set; }
public string? FeaturesTitle { get; set; }
public List<FeatureItem>? Features { get; set; }
public string? StatsTitle { get; set; }
public List<StatSettingItem>? Stats { get; set; }
public string? TestimonialsTitle { get; set; }
public List<TestimonialSettingItem>? Testimonials { get; set; }
public string? FaqTitle { get; set; }
public List<FaqItem>? Faqs { get; set; }
public string? CtaTitle { get; set; }
public string? CtaDescription { get; set; }
public string? CtaButtonText { get; set; }
public bool? FeaturedBlogEnabled { get; set; }
}
private class TrustBadgeItem { public string? IconName { get; set; } public string? Text { get; set; } }
private class StepItem { public string? Title { get; set; } public string? Description { get; set; } }
private class FeatureItem { public string? IconName { get; set; } public string? Title { get; set; } public string? Description { get; set; } }
private class StatSettingItem { public string? Label { get; set; } public double Value { get; set; } public string? Suffix { get; set; } public string? Color { get; set; } }
private class TestimonialSettingItem { public string? Quote { get; set; } public string? Name { get; set; } public string? Role { get; set; } }
private class FaqItem { public string? Question { get; set; } public string? Answer { get; set; } }
public void Dispose()
{
MainService.OnChangeHandler -= OnStateChanged;