feat: full FrontOffice updates - discount store, blog, payment gateway, UI improvements
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 4m55s
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user