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
+116 -1
View File
@@ -1,6 +1,7 @@
using Blazored.LocalStorage;
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.JSInterop;
using MudBlazor;
using Microsoft.AspNetCore.Components.Authorization;
@@ -16,6 +17,15 @@ public partial class MainLayout : IDisposable
private bool _isAuthenticated;
private string? _email;
private int _cartCount;
private int _discountCartCount;
private bool _isCompleteRegister;
// ── Store Context ──
private enum StoreContext { None, Regular, Discount }
private StoreContext _storeCtx = StoreContext.None;
private bool IsInRegularStore => _storeCtx == StoreContext.Regular;
private bool IsInDiscountStore => _storeCtx == StoreContext.Discount;
private bool IsInAnyStore => _storeCtx != StoreContext.None;
// متغیرهای بروزرسانی
private bool _hasUpdate;
@@ -26,6 +36,7 @@ public partial class MainLayout : IDisposable
[Inject] private AuthService AuthService { get; set; } = default!;
[Inject] private AuthDialogService AuthDialogService { get; set; } = default!;
[Inject] private CartService CartService { get; set; } = default!;
[Inject] private DiscountCartService DiscountCartService { get; set; } = default!;
[Inject] private AppVersionService AppVersionService { get; set; } = default!;
private void ToggleTheme() => _isDark = !_isDark;
@@ -35,20 +46,34 @@ public partial class MainLayout : IDisposable
await JSRuntime.InvokeVoidAsync("history.back");
}
protected override void OnInitialized()
{
Navigation.LocationChanged += OnLocationChanged;
UpdateStoreContext();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("changeNavBgOnBodyScroll", "top", null, 1);
await CheckAuthStatus();
await EnforceAuthGuardAsync();
if (_isAuthenticated)
{
// بررسی وضعیت ثبت‌نام کامل
_isCompleteRegister = await AuthService.IsCompleteRegisterAsync();
// لود سبد خرید فقط برای کاربر لاگین شده
await CartService.EnsureInitializedAsync();
CartService.OnChange += OnCartChanged;
_cartCount = CartService.Count;
await DiscountCartService.EnsureInitializedAsync();
DiscountCartService.OnChange += OnDiscountCartChanged;
_discountCartCount = DiscountCartService.Count;
// چک کردن بروزرسانی (بدون نمایش popup - فقط برای نشون دادن آیکون)
await CheckForUpdateSilentlyAsync();
}
@@ -57,6 +82,83 @@ public partial class MainLayout : IDisposable
}
}
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
UpdateStoreContext();
_ = InvokeAsync(async () =>
{
await EnforceAuthGuardAsync();
StateHasChanged();
});
}
/// <summary>
/// If the current route requires authentication and the user is not logged in, redirect to home.
/// </summary>
private async Task EnforceAuthGuardAsync()
{
if (_isAuthenticated) return;
// Re-check in case token was set after initial render
_isAuthenticated = await AuthService.IsAuthenticatedAsync();
if (_isAuthenticated) return;
var path = new Uri(Navigation.Uri).AbsolutePath.ToLowerInvariant();
if (IsProtectedRoute(path))
{
Navigation.NavigateTo(RouteConstants.Main.MainPage, replace: true);
}
}
private static bool IsProtectedRoute(string path)
{
// Public routes — no auth needed
if (path is "/" or "/register" or "/about" or "/faq" or "/contact"
or "/packages" or "/stores" or "/products" or "/categories")
return false;
if (path.StartsWith("/blog")) return false;
if (path.StartsWith("/package/")) return false;
if (path.StartsWith("/product/")) return false;
if (path == "/discount-store" || path.StartsWith("/discount-store/product/")) return false;
// Everything else is protected
if (path.StartsWith("/profile")) return true;
if (path.StartsWith("/commission")) return true;
if (path.StartsWith("/network")) return true;
if (path.StartsWith("/club")) return true;
if (path.StartsWith("/checkout")) return true;
if (path.StartsWith("/cart")) return true;
if (path.StartsWith("/orders") || path.StartsWith("/order/") || path.StartsWith("/order-tracking/")) return true;
if (path.StartsWith("/my-packages") || path.StartsWith("/my-orders") || path.StartsWith("/my-cart")) return true;
if (path.StartsWith("/discount-store/cart") || path.StartsWith("/discount-store/checkout")
|| path.StartsWith("/discount-store/orders") || path.StartsWith("/discount-store/order/")) return true;
return false;
}
private void UpdateStoreContext()
{
var path = new Uri(Navigation.Uri).AbsolutePath.ToLowerInvariant();
// Discount store routes all start with /discount-store
if (path.StartsWith("/discount-store"))
{
_storeCtx = StoreContext.Discount;
}
// Regular store routes
else if (path.StartsWith("/products") || path.StartsWith("/product/") ||
path.StartsWith("/cart") || path.StartsWith("/checkout-summary") ||
path.StartsWith("/orders") || path.StartsWith("/order/") ||
path.StartsWith("/order-tracking/") || path.StartsWith("/categories"))
{
_storeCtx = StoreContext.Regular;
}
else
{
_storeCtx = StoreContext.None;
}
}
/// <summary>
/// چک کردن بروزرسانی بدون نمایش popup
/// </summary>
@@ -131,6 +233,12 @@ public partial class MainLayout : IDisposable
InvokeAsync(StateHasChanged);
}
private void OnDiscountCartChanged()
{
_discountCartCount = DiscountCartService.Count;
InvokeAsync(StateHasChanged);
}
private async Task CheckAuthStatus()
{
_isAuthenticated = await AuthService.IsAuthenticatedAsync();
@@ -154,6 +262,11 @@ public partial class MainLayout : IDisposable
Navigation.NavigateTo(RouteConstants.Profile.Index);
}
private void NavigateToHub()
{
Navigation.NavigateTo(RouteConstants.Profile.Hub);
}
private async Task Logout()
{
await AuthService.LogoutAsync();
@@ -165,5 +278,7 @@ public partial class MainLayout : IDisposable
public void Dispose()
{
CartService.OnChange -= OnCartChanged;
DiscountCartService.OnChange -= OnDiscountCartChanged;
Navigation.LocationChanged -= OnLocationChanged;
}
}