231da2cbaa
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>
126 lines
4.4 KiB
C#
126 lines
4.4 KiB
C#
using Microsoft.AspNetCore.Components;
|
|
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
|
using FrontOffice.Main.Utilities;
|
|
using MudBlazor;
|
|
|
|
namespace FrontOffice.Main.Pages.DiscountStore;
|
|
|
|
public partial class Checkout
|
|
{
|
|
[Inject] private DiscountCartService DiscountCart { get; set; } = default!;
|
|
[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;
|
|
private bool _loadingAddresses;
|
|
|
|
private string? _notes;
|
|
private bool _placing;
|
|
|
|
/// <summary>مبلغ درگاه قبل از مالیات (جمع کل - اعتبار)</summary>
|
|
private long NetGatewayAmount => DiscountCart.TotalPrice - DiscountCart.TotalDiscount;
|
|
|
|
/// <summary>مالیات بر ارزش افزوده</summary>
|
|
private long VatAmount => VAT.CalculateVAT(NetGatewayAmount);
|
|
|
|
/// <summary>مبلغ نهایی قابل پرداخت (شامل VAT)</summary>
|
|
private long FinalGatewayAmount => NetGatewayAmount + VatAmount;
|
|
|
|
private bool CanPlaceOrder => DiscountCart.Items.Count > 0 && _selectedAddress is not null;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
if (!await AuthService.IsAuthenticatedAsync())
|
|
{
|
|
await AuthDialogService.ShowAuthDialogAsync();
|
|
}
|
|
await VAT.LoadAsync();
|
|
await DiscountCart.EnsureInitializedAsync();
|
|
await LoadAddresses();
|
|
}
|
|
|
|
private async Task LoadAddresses()
|
|
{
|
|
_loadingAddresses = true;
|
|
try
|
|
{
|
|
var response = await UserAddressContract.GetCustomerAddressesAsync(new());
|
|
if (response?.Models?.Any() == true)
|
|
{
|
|
_addresses = response.Models.ToList();
|
|
_selectedAddress = _addresses.FirstOrDefault(a => a.IsDefault) ?? _addresses.First();
|
|
}
|
|
else
|
|
{
|
|
_addresses = new();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"خطا در بارگذاری آدرسها: {ex.Message}", Severity.Error);
|
|
_addresses = new();
|
|
}
|
|
finally
|
|
{
|
|
_loadingAddresses = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
}
|
|
|
|
private async Task PlaceOrder()
|
|
{
|
|
if (!CanPlaceOrder || _selectedAddress is null)
|
|
{
|
|
Snackbar.Add("لطفاً آدرس را انتخاب کنید.", Severity.Warning);
|
|
return;
|
|
}
|
|
|
|
_placing = true;
|
|
try
|
|
{
|
|
// Always use 100% discount — send full price, server will apply max from wallet
|
|
var result = await DiscountOrderService.PlaceOrderAsync(
|
|
_selectedAddress.Id,
|
|
DiscountCart.TotalPrice,
|
|
_notes);
|
|
|
|
if (!result.Success)
|
|
{
|
|
Snackbar.Add(result.Message, Severity.Error);
|
|
return;
|
|
}
|
|
|
|
// If there's a gateway payment URL, redirect to it
|
|
if (!string.IsNullOrWhiteSpace(result.PaymentUrl) && result.GatewayAmount > 0)
|
|
{
|
|
Snackbar.Add("در حال انتقال به درگاه پرداخت...", Severity.Info);
|
|
await DiscountCart.ClearAsync();
|
|
Navigation.NavigateTo(result.PaymentUrl, forceLoad: true);
|
|
return;
|
|
}
|
|
|
|
// If fully paid via discount balance (no gateway needed)
|
|
await DiscountCart.ClearAsync();
|
|
Snackbar.Add("سفارش با موفقیت ثبت شد!", Severity.Success);
|
|
Navigation.NavigateTo($"{RouteConstants.DiscountStore.OrderDetail}{result.OrderId}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"خطا در ثبت سفارش: {ex.Message}", Severity.Error);
|
|
}
|
|
finally
|
|
{
|
|
_placing = false;
|
|
}
|
|
}
|
|
|
|
private static string FormatPrice(long price) => $"{price:N0}";
|
|
|
|
private static string GetImageUrl(string? imageUrl)
|
|
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
|
|
}
|