refactor: migrate Landing page to SitePageSettings system
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 6m33s
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:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user