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
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:
@@ -56,6 +56,7 @@ public static class ConfigureServices
|
||||
services.AddSingleton<UserAuthInfo>();
|
||||
services.AddScoped<AuthService>();
|
||||
services.AddScoped<AuthDialogService>();
|
||||
services.AddScoped<GuestActionGate>();
|
||||
// Storefront services
|
||||
services.AddScoped<CartService>();
|
||||
services.AddScoped<ProductService>();
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccountBalanceWallet" Color="Color.Success" Size="Size.Large" />
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.subtitle1">شارژ کیف پول فروشگاه اعتباری</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Default">شارژ ۵۶ میلیون تومان کیف پول فروشگاه اعتباری</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Default">شارژ برابر ارزش پکیج فعال در کیف پول فروشگاه اعتباری</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
<MudDivider />
|
||||
|
||||
@@ -7,9 +7,15 @@ public partial class Cart : IDisposable
|
||||
{
|
||||
[Inject] private DiscountCartService DiscountCart { get; set; } = default!;
|
||||
[Inject] private VATService VAT { get; set; } = default!;
|
||||
[Inject] private AuthDialogService AuthDialogService { get; set; } = default!;
|
||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!await AuthService.IsAuthenticatedAsync())
|
||||
{
|
||||
await AuthDialogService.ShowAuthDialogAsync();
|
||||
}
|
||||
await DiscountCart.EnsureInitializedAsync();
|
||||
DiscountCart.OnChange += StateHasChanged;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ public partial class Checkout
|
||||
[Inject] private DiscountOrderService DiscountOrderService { get; set; } = default!;
|
||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
||||
[Inject] private VATService VAT { get; set; } = default!;
|
||||
[Inject] private AuthDialogService AuthDialogService { get; set; } = default!;
|
||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||
|
||||
private List<CustomerAddressModel> _addresses = new();
|
||||
private CustomerAddressModel? _selectedAddress;
|
||||
@@ -32,6 +34,10 @@ public partial class Checkout
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!await AuthService.IsAuthenticatedAsync())
|
||||
{
|
||||
await AuthDialogService.ShowAuthDialogAsync();
|
||||
}
|
||||
await VAT.LoadAsync();
|
||||
await DiscountCart.EnsureInitializedAsync();
|
||||
await LoadAddresses();
|
||||
|
||||
@@ -10,6 +10,7 @@ public partial class ProductDetail
|
||||
[Inject] private DiscountProductService DiscountProductService { get; set; } = default!;
|
||||
[Inject] private DiscountCartService DiscountCartService { get; set; } = default!;
|
||||
[Inject] private VATService VAT { get; set; } = default!;
|
||||
[Inject] private GuestActionGate GuestGate { get; set; } = default!;
|
||||
|
||||
private DiscountProductDetail? _product;
|
||||
private string _selectedImage = string.Empty;
|
||||
@@ -59,8 +60,14 @@ public partial class ProductDetail
|
||||
_addingToCart = true;
|
||||
try
|
||||
{
|
||||
await DiscountCartService.AddAsync(_product.Id, _quantity);
|
||||
Snackbar.Add($"{_product.Title} به سبد خرید اضافه شد", MudBlazor.Severity.Success);
|
||||
var productId = _product.Id;
|
||||
var qty = _quantity;
|
||||
var title = _product.Title;
|
||||
await GuestGate.RunAsync(async () =>
|
||||
{
|
||||
await DiscountCartService.AddAsync(productId, qty);
|
||||
Snackbar.Add($"{title} به سبد خرید اضافه شد", MudBlazor.Severity.Success);
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ public partial class Products : ComponentBase, IDisposable
|
||||
{
|
||||
[Inject] private DiscountProductService ProductService { get; set; } = default!;
|
||||
[Inject] private DiscountCartService DiscountCart { get; set; } = default!;
|
||||
[Inject] private GuestActionGate GuestGate { get; set; } = default!;
|
||||
|
||||
private string _search = string.Empty;
|
||||
private long? _selectedCategoryId;
|
||||
@@ -94,7 +95,7 @@ public partial class Products : ComponentBase, IDisposable
|
||||
|
||||
private async Task AddToCart(DiscountProductCard p)
|
||||
{
|
||||
await DiscountCart.AddAsync(p.Id);
|
||||
await GuestGate.RunAsync(() => DiscountCart.AddAsync(p.Id));
|
||||
}
|
||||
|
||||
private void NavigateToProduct(long id)
|
||||
|
||||
@@ -93,6 +93,161 @@
|
||||
</section>
|
||||
}
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
1c. TOP-SELLING REGULAR PRODUCTS
|
||||
═══════════════════════════════════════════════ *@
|
||||
@if (_loadingTopProducts)
|
||||
{
|
||||
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-4">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Small" />
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
</section>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (_topRegularProducts.Any())
|
||||
{
|
||||
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<div class="text-center mb-6 fade-in-up">
|
||||
<MudText Typo="Typo.h3">پرخریدترین محصولات فروشگاه</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||
محبوبترین محصولات کارا بازار سلامت
|
||||
</MudText>
|
||||
</div>
|
||||
|
||||
<MudGrid Spacing="2" Justify="Justify.FlexStart">
|
||||
@foreach (var p in _topRegularProducts)
|
||||
{
|
||||
<MudItem xs="6" sm="6" md="4">
|
||||
<MudCard Class="rounded-lg h-100 d-flex flex-column overflow-hidden landing-product-card"
|
||||
Style="cursor:pointer;"
|
||||
@onclick="() => NavigateToRegularProduct(p.Id)">
|
||||
<MudCardContent Class="d-flex flex-column pa-1 h-100">
|
||||
<div style="aspect-ratio:1/1;width:100%;background-image:url('@p.ImageUrl');background-size:cover;background-position:center;border-radius:0.5rem;position:relative;">
|
||||
@if (p.RemainingCount <= 0)
|
||||
{
|
||||
<div style="position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;border-radius:0.5rem;">
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small">ناموجود</MudChip>
|
||||
</div>
|
||||
}
|
||||
else if (p.RemainingCount <= 5)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Warning" Variant="Variant.Filled" Size="Size.Small"
|
||||
Style="position:absolute;top:8px;right:8px;">
|
||||
فقط @p.RemainingCount عدد
|
||||
</MudChip>
|
||||
}
|
||||
</div>
|
||||
<div class="pa-1 flex-grow-1 d-flex flex-column justify-space-between">
|
||||
<MudText Typo="Typo.subtitle1" Style="display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;">@p.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Primary">@FormatPrice(p.Price)</MudText>
|
||||
</div>
|
||||
</MudCardContent>
|
||||
<MudCardActions Class="mt-auto pa-2" @onclick:stopPropagation="true">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.AddShoppingCart"
|
||||
Disabled="@(p.RemainingCount <= 0)"
|
||||
OnClick="@(() => AddRegularToCart(p))">
|
||||
@(p.RemainingCount <= 0 ? "ناموجود" : "افزودن به سبد")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
|
||||
<div class="text-center mt-6">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Class="rounded-pill"
|
||||
Href="@RouteConstants.Store.Products"
|
||||
EndIcon="@Icons.Material.Filled.ArrowBack">
|
||||
مشاهده همه محصولات
|
||||
</MudButton>
|
||||
</div>
|
||||
</MudContainer>
|
||||
</section>
|
||||
}
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
1d. TOP-SELLING DISCOUNT STORE PRODUCTS
|
||||
═══════════════════════════════════════════════ *@
|
||||
@if (_topDiscountProducts.Any())
|
||||
{
|
||||
<section class="section-landing">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<div class="text-center mb-6 fade-in-up">
|
||||
<MudText Typo="Typo.h3">پرخریدترین محصولات فروشگاه اعتباری</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||
محصولات ویژه اعضای باشگاه با پرداخت اعتباری
|
||||
</MudText>
|
||||
</div>
|
||||
|
||||
<MudGrid Spacing="2" Justify="Justify.FlexStart">
|
||||
@foreach (var p in _topDiscountProducts)
|
||||
{
|
||||
<MudItem xs="6" sm="6" md="4">
|
||||
<MudCard Class="rounded-lg h-100 d-flex flex-column overflow-hidden landing-product-card"
|
||||
Style="cursor:pointer;"
|
||||
@onclick="() => NavigateToDiscountProduct(p.Id)">
|
||||
<MudCardContent Class="d-flex flex-column pa-1 h-100">
|
||||
<div style="aspect-ratio:1/1;width:100%;background-image:url('@(string.IsNullOrWhiteSpace(p.ThumbnailUrl) ? p.ImageUrl : p.ThumbnailUrl)');background-size:cover;background-position:center;border-radius:0.5rem;position:relative;">
|
||||
@if (p.RemainingCount <= 0)
|
||||
{
|
||||
<div style="position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;border-radius:0.5rem;">
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small">ناموجود</MudChip>
|
||||
</div>
|
||||
}
|
||||
else if (p.RemainingCount <= 5)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Warning" Variant="Variant.Filled" Size="Size.Small"
|
||||
Style="position:absolute;top:8px;right:8px;">
|
||||
فقط @p.RemainingCount عدد
|
||||
</MudChip>
|
||||
}
|
||||
@if (p.MaxDiscountPercent > 0)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Size="Size.Small"
|
||||
Style="position:absolute;top:8px;left:8px;">
|
||||
@p.MaxDiscountPercent% اعتبار
|
||||
</MudChip>
|
||||
}
|
||||
</div>
|
||||
<div class="pa-1 flex-grow-1 d-flex flex-column justify-space-between">
|
||||
<MudText Typo="Typo.subtitle1" Style="display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;">@p.Title</MudText>
|
||||
<div>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Primary">@FormatDiscountPrice(p.Price)</MudText>
|
||||
<MudText Typo="Typo.overline" Class="mud-text-secondary" Style="font-size:0.6rem;line-height:1;">(+ ارزش افزوده)</MudText>
|
||||
</div>
|
||||
</div>
|
||||
</MudCardContent>
|
||||
<MudCardActions Class="mt-auto pa-2" @onclick:stopPropagation="true">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Secondary" FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.AddShoppingCart"
|
||||
Disabled="@(p.RemainingCount <= 0)"
|
||||
OnClick="@(() => AddDiscountToCart(p))">
|
||||
@(p.RemainingCount <= 0 ? "ناموجود" : "افزودن به سبد")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
|
||||
<div class="text-center mt-6">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Secondary" Class="rounded-pill"
|
||||
Href="@RouteConstants.DiscountStore.Products"
|
||||
EndIcon="@Icons.Material.Filled.ArrowBack">
|
||||
مشاهده همه محصولات اعتباری
|
||||
</MudButton>
|
||||
</div>
|
||||
</MudContainer>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
2. HOW IT WORKS — 3 numbered steps
|
||||
═══════════════════════════════════════════════ *@
|
||||
|
||||
@@ -10,11 +10,22 @@ 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!;
|
||||
[Inject] private ProductService ProductService { get; set; } = default!;
|
||||
[Inject] private DiscountProductService DiscountProductService { get; set; } = default!;
|
||||
[Inject] private CartService Cart { get; set; } = default!;
|
||||
[Inject] private DiscountCartService DiscountCart { get; set; } = default!;
|
||||
[Inject] private GuestActionGate GuestGate { get; set; } = default!;
|
||||
[Inject] private VATService VAT { get; set; } = default!;
|
||||
|
||||
// ── CMS page data ──
|
||||
private PageSettingsDto? _pageData;
|
||||
private LandingSettings? _settings;
|
||||
|
||||
// ── Top-selling product sections ──
|
||||
private List<Product> _topRegularProducts = new();
|
||||
private List<DiscountProductCard> _topDiscountProducts = new();
|
||||
private bool _loadingTopProducts = true;
|
||||
|
||||
// ── Latest blog posts (loaded from CMS) ──
|
||||
private List<BlogPostCardDto> _latestPosts = new();
|
||||
|
||||
@@ -51,10 +62,29 @@ public partial class Index : IDisposable
|
||||
PopulateFromSettings();
|
||||
_dataLoaded = true;
|
||||
|
||||
// Load top-selling products and blog posts in parallel
|
||||
var topRegTask = ProductService.GetTopSellingAsync(6);
|
||||
var topDiscTask = DiscountProductService.GetTopSellingAsync(6);
|
||||
var featuredPostsTask = BlogPostService.GetFeaturedPostsAsync(2);
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(topRegTask, topDiscTask, featuredPostsTask);
|
||||
_topRegularProducts = topRegTask.Result.Products;
|
||||
_topDiscountProducts = topDiscTask.Result.Products;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback: sections remain empty
|
||||
}
|
||||
_loadingTopProducts = false;
|
||||
|
||||
// Load latest published blog posts
|
||||
try
|
||||
{
|
||||
_latestPosts = await BlogPostService.GetFeaturedPostsAsync(2);
|
||||
_latestPosts = featuredPostsTask.IsCompletedSuccessfully
|
||||
? featuredPostsTask.Result
|
||||
: await BlogPostService.GetFeaturedPostsAsync(2);
|
||||
|
||||
if (_latestPosts.Count < 2)
|
||||
{
|
||||
@@ -267,6 +297,21 @@ public partial class Index : IDisposable
|
||||
Navigation.NavigateTo($"/blog/{slug}");
|
||||
}
|
||||
|
||||
private void NavigateToRegularProduct(long id)
|
||||
=> Navigation.NavigateTo(RouteConstants.Store.ProductDetail + id);
|
||||
|
||||
private void NavigateToDiscountProduct(long id)
|
||||
=> Navigation.NavigateTo(RouteConstants.DiscountStore.ProductDetail + id);
|
||||
|
||||
private async Task AddRegularToCart(Product p)
|
||||
=> await GuestGate.RunAsync(() => Cart.Add(p, 1));
|
||||
|
||||
private async Task AddDiscountToCart(DiscountProductCard p)
|
||||
=> await GuestGate.RunAsync(() => DiscountCart.AddAsync(p.Id));
|
||||
|
||||
private string FormatPrice(long price) => $"{VAT.AddVAT(price):N0} تومان";
|
||||
private string FormatDiscountPrice(long price) => $"{price:N0} تومان";
|
||||
|
||||
private async void OnStateChanged()
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
@@ -8,11 +8,17 @@ public partial class Cart : ComponentBase, IDisposable
|
||||
{
|
||||
[Inject] private CartService CartService { get; set; } = default!;
|
||||
[Inject] private VATService VAT { get; set; } = default!;
|
||||
[Inject] private AuthDialogService AuthDialogService { get; set; } = default!;
|
||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||
// Navigation and Snackbar are available via _Imports.razor
|
||||
private CartService CartData => CartService;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!await AuthService.IsAuthenticatedAsync())
|
||||
{
|
||||
await AuthDialogService.ShowAuthDialogAsync();
|
||||
}
|
||||
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
|
||||
await CartService.EnsureInitializedAsync();
|
||||
CartService.OnChange += StateHasChanged;
|
||||
|
||||
@@ -14,6 +14,8 @@ public partial class CheckoutSummary : ComponentBase
|
||||
[Inject] private VATService VAT { get; set; } = default!;
|
||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
||||
[Inject] private UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
|
||||
[Inject] private AuthDialogService AuthDialogService { get; set; } = default!;
|
||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||
// Snackbar and Navigation are injected via _Imports.razor
|
||||
|
||||
private List<CustomerAddressModel> _addresses = new();
|
||||
@@ -27,6 +29,10 @@ public partial class CheckoutSummary : ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!await AuthService.IsAuthenticatedAsync())
|
||||
{
|
||||
await AuthDialogService.ShowAuthDialogAsync();
|
||||
}
|
||||
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
|
||||
await Cart.EnsureInitializedAsync();
|
||||
await LoadAddresses();
|
||||
|
||||
@@ -12,6 +12,7 @@ public partial class ProductDetail : ComponentBase, IDisposable
|
||||
[Inject] private ProductService ProductService { get; set; } = default!;
|
||||
[Inject] private CartService Cart { get; set; } = default!;
|
||||
[Inject] private VATService VAT { get; set; } = default!;
|
||||
[Inject] private GuestActionGate GuestGate { get; set; } = default!;
|
||||
|
||||
[Parameter] public long id { get; set; }
|
||||
|
||||
@@ -92,14 +93,18 @@ public partial class ProductDetail : ComponentBase, IDisposable
|
||||
private async Task AddToCart()
|
||||
{
|
||||
if (_product is null) return;
|
||||
await Cart.Add(_product, 1);
|
||||
var product = _product;
|
||||
await GuestGate.RunAsync(() => Cart.Add(product, 1));
|
||||
}
|
||||
|
||||
private async Task RemoveFromCart()
|
||||
{
|
||||
if (_product is null) return;
|
||||
await GuestGate.RunAsync(async () =>
|
||||
{
|
||||
_qty--;
|
||||
await Cart.UpdateQuantity(CurrentCartItem.ProductId, _qty);
|
||||
await Cart.UpdateQuantity(CurrentCartItem!.ProductId, _qty);
|
||||
});
|
||||
}
|
||||
|
||||
private void IncreaseLocalQty()
|
||||
|
||||
@@ -21,6 +21,7 @@ public partial class Products : ComponentBase, IDisposable
|
||||
[Inject] private CategoryService CategoryService { get; set; } = default!;
|
||||
[Inject] private CartService Cart { get; set; } = default!;
|
||||
[Inject] private VATService VAT { get; set; } = default!;
|
||||
[Inject] private GuestActionGate GuestGate { get; set; } = default!;
|
||||
|
||||
private string _query = string.Empty;
|
||||
private bool _loading;
|
||||
@@ -112,7 +113,7 @@ public partial class Products : ComponentBase, IDisposable
|
||||
|
||||
private async Task AddToCart(Product p)
|
||||
{
|
||||
await Cart.Add(p, 1);
|
||||
await GuestGate.RunAsync(() => Cart.Add(p, 1));
|
||||
}
|
||||
|
||||
private string FormatPrice(long price) => $"{VAT.AddVAT(price):N0} تومان";
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user