feat: full FrontOffice updates - discount store, blog, payment gateway, UI improvements
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 4m55s

- Discount store pages (products, cart, orders, order detail)
- Blog pages and services
- Payment gateway callback page
- AppImage component, EmptyState, LoadingState, PageHeader
- DiscountCartService, DiscountOrderService, DiscountProductService
- BlogCategoryService, BlogPostService, SitePageService, ImageCacheService
- PhoneVerifyForm component
- Profile Hub page
- UI/UX improvements across all pages
- landing.js for homepage
- Config and routing updates
This commit is contained in:
masoodafar-web
2026-02-16 00:51:39 +03:30
parent f0d1156457
commit 6db83ccd90
104 changed files with 7668 additions and 1370 deletions
+107 -81
View File
@@ -1,44 +1,66 @@
using CMSMicroservice.Protobuf.Protos.Package;
using FrontOffice.Main.Utilities;
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using MudBlazor;
using System.Security.Cryptography;
namespace FrontOffice.Main.Pages;
public partial class Index:IDisposable
public partial class Index : IDisposable
{
[Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!;
[Inject] private MobileNumberEncryptor Encryptor { get; set; }
private string? _email;
private bool _isLoadingPackages;
private List<Pack> _packs = new();
[Inject] private BlogPostService BlogPostService { get; set; } = default!;
[Inject] private AuthService AuthService { get; set; } = default!;
// ── Latest blog posts (loaded from CMS) ──
private List<BlogPostCardDto> _latestPosts = new();
protected override async Task OnInitializedAsync()
{
MainService.OnChangeHandler +=async () =>
MainService.OnChangeHandler += OnStateChanged;
// Load latest published blog posts
// اول پین‌شده‌ها، اگر کافی نبود آخرین پست‌ها اضافه شود
try
{
await InvokeAsync(StateHasChanged);
};
// await LoadPackagesAsync();
//string mobileNumber = "09387342688";
//// انکریپت کردن
//string encrypted = Encryptor.EncryptMobileNumber(mobileNumber);
//Console.WriteLine($"Encrypted: {encrypted}");
//// دیکریپت کردن برای تست
//string decrypted = Encryptor.DecryptMobileNumber(encrypted);
//Console.WriteLine($"Decrypted: {decrypted}");
_latestPosts = await BlogPostService.GetFeaturedPostsAsync(2);
// اگر فقط ۱ پین‌شده داریم، ۱ پست آخر هم اضافه کن
if (_latestPosts.Count < 2)
{
var result = await BlogPostService.GetPublishedPostsAsync(page: 1, pageSize: 2);
var existing = _latestPosts.Select(p => p.Id).ToHashSet();
foreach (var post in result.Posts)
{
if (!existing.Contains(post.Id))
{
_latestPosts.Add(post);
if (_latestPosts.Count >= 2) break;
}
}
}
}
catch
{
_latestPosts = new();
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Init scroll-triggered fade-in animations
await JS.InvokeVoidAsync("initScrollAnimations");
// Animate stat counters
foreach (var stat in _stats)
{
await JS.InvokeVoidAsync("animateCounter", stat.ElementId, stat.Target, 2200, stat.Suffix);
}
}
if (await AuthService.IsAuthenticatedAsync())
{
if ((await AuthService.IsCompleteRegisterAsync()))
if (await AuthService.IsCompleteRegisterAsync())
{
Navigation.NavigateTo(RouteConstants.Profile.Index);
}
@@ -47,56 +69,8 @@ public partial class Index:IDisposable
Navigation.NavigateTo(RouteConstants.Registration.Wizard);
}
}
await base.OnAfterRenderAsync(firstRender);
}
// private async Task LoadPackagesAsync()
// {
// _isLoadingPackages = true;
// try
// {
// var response = await PackageClient.GetAllPackageByFilterAsync(request: new());
// if (response?.Models?.Any() == true)
// {
// _packs = response.Models.Select(p => new Pack(
// Id: p.Id,
// Title: p.Title,
// Body: p.Description,
// Image: UrlUtility.DownloadUrl + p.ImagePath,
// Price: p.Price
// )).ToList();
// }
// else
// _packs = new List<Pack>();
// }
// catch (Exception ex)
// {
// Snackbar.Add($"خطا در بارگذاری پکیج‌ها: {ex.Message}", Severity.Error);
// // Fallback to empty list
// _packs = new List<Pack>();
// }
// finally
// {
// _isLoadingPackages = false;
// await InvokeAsync(StateHasChanged);
// }
// }
private void JoinWaitlist()
{
if (string.IsNullOrWhiteSpace(_email))
{
Snackbar.Add("لطفاً ایمیل معتبر وارد کنید.", Severity.Warning);
return;
}
Snackbar.Add("به لیست انتظار «کارا بازار سلامت» اضافه شدید.", Severity.Success);
_email = string.Empty;
}
private void NavigateToPackage(long packageId)
{
Navigation.NavigateTo($"{RouteConstants.Package.Detail}{packageId}");
await base.OnAfterRenderAsync(firstRender);
}
private void NavigateToRegistrationWizard()
@@ -104,25 +78,77 @@ public partial class Index:IDisposable
Navigation.NavigateTo(RouteConstants.Registration.Wizard);
}
private record Pack(long Id, string Title, string Body, string Image, long Price);
private void NavigateToPost(string slug)
{
Navigation.NavigateTo($"/blog/{slug}");
}
private record Plan(string Name, string Price, bool Highlight, IEnumerable<string> Features);
private async void OnStateChanged()
{
await InvokeAsync(StateHasChanged);
}
private record QA(string Q, string A);
// ── 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("دامنه اختصاصی دارم؛ قابل اتصال است؟", "بله، پشت دامنه و گواهی 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);
public void Dispose()
{
MainService.OnChangeHandler -=async () =>
{
await InvokeAsync(StateHasChanged);
};
MainService.OnChangeHandler -= OnStateChanged;
}
}