feat: add top-seller product sections to landing page with guest browsing support
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 3m10s

- Add two sections to landing page (Index.razor) below the hero:
  - Top 6 best-selling regular products (3-per-row grid)
  - Top 6 best-selling discount store products (3-per-row grid)
  - Each section has a 'More' button linking to /products and /discount-store
- Add GuestActionGate utility service: wraps auth-required actions; shows
  login modal for guests and resumes the original action after successful login
- Register GuestActionGate as scoped service in ConfigureServices
- Add GetTopSellingAsync(count) to ProductService and DiscountProductService
- Add optional sortBy parameter to DiscountProductService.GetProductsAsync
- Hybridize all product pages for guest browsing:
  - Store/Products, Store/ProductDetail: wrap AddToCart with GuestActionGate
  - DiscountStore/Products, DiscountStore/ProductDetail: wrap AddToCart with GuestActionGate
  - Store/Cart, DiscountStore/Cart, Store/CheckoutSummary: soft auth gate on page init
- Fix MembershipPage membership benefit text to be package-agnostic

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
masoodafar-web
2026-05-13 19:48:14 +03:30
parent a1d67c5865
commit 231da2cbaa
15 changed files with 299 additions and 10 deletions
@@ -80,7 +80,7 @@ public class DiscountProductService
public async Task<DiscountProductListResult> GetProductsAsync(
int page = 1, int pageSize = 12,
string? search = null, long? categoryId = null,
bool? inStock = null)
bool? inStock = null, string? sortBy = null)
{
try
{
@@ -97,6 +97,8 @@ public class DiscountProductService
request.CategoryId = categoryId.Value;
if (inStock.HasValue)
request.InStock = inStock.Value;
if (!string.IsNullOrWhiteSpace(sortBy))
request.SortBy = sortBy;
var response = await _productClient.GetDiscountProductsAsync(request);
@@ -125,6 +127,9 @@ public class DiscountProductService
}
}
public Task<DiscountProductListResult> GetTopSellingAsync(int count = 6)
=> GetProductsAsync(page: 1, pageSize: count, sortBy: "SaleCount desc");
public async Task<DiscountProductDetail?> GetByIdAsync(long productId)
{
try
@@ -0,0 +1,42 @@
namespace FrontOffice.Main.Utilities;
/// <summary>
/// هر action ای که نیاز به لاگین دارد را از طریق این سرویس اجرا کنید.
/// اگر کاربر لاگین نباشد، مودال ورود نشان داده می‌شود.
/// پس از ورود موفق، action به صورت خودکار اجرا می‌شود.
/// </summary>
public class GuestActionGate
{
private readonly AuthService _authService;
private readonly AuthDialogService _authDialogService;
public GuestActionGate(AuthService authService, AuthDialogService authDialogService)
{
_authService = authService;
_authDialogService = authDialogService;
}
/// <summary>
/// اگر کاربر لاگین باشد action را اجرا می‌کند.
/// در غیر این صورت مودال لاگین را باز کرده و پس از ورود موفق، action را اجرا می‌کند.
/// </summary>
/// <returns>true اگر action اجرا شد، false اگر کاربر لاگین نکرد.</returns>
public async Task<bool> RunAsync(Func<Task> action)
{
if (await _authService.IsAuthenticatedAsync())
{
await action();
return true;
}
await _authDialogService.ShowAuthDialogAsync();
if (await _authService.IsAuthenticatedAsync())
{
await action();
return true;
}
return false;
}
}
@@ -71,6 +71,9 @@ public class ProductService
return result.Products;
}
public Task<ProductListResult> GetTopSellingAsync(int count = 6)
=> GetProductsPagedAsync(sortBy: "SaleCount desc", page: 1, pageSize: count);
public async Task<ProductListResult> GetProductsPagedAsync(
string? query = null, long? categoryId = null, string? sortBy = null,
int page = 1, int pageSize = 12)