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:
@@ -0,0 +1,146 @@
|
||||
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!;
|
||||
|
||||
private List<CustomerAddressModel> _addresses = new();
|
||||
private CustomerAddressModel? _selectedAddress;
|
||||
private bool _loadingAddresses;
|
||||
|
||||
private long _discountBalance;
|
||||
private long _discountBalanceToUse;
|
||||
private string? _notes;
|
||||
private bool _placing;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum discount allowed based on cart items' MaxDiscountPercent
|
||||
/// </summary>
|
||||
private long MaxAllowedDiscount => DiscountCart.TotalDiscount;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum the user can actually use = min(balance, allowed discount)
|
||||
/// </summary>
|
||||
private long MaxUsable => Math.Min(_discountBalance, MaxAllowedDiscount);
|
||||
|
||||
/// <summary>
|
||||
/// Amount to be charged via payment gateway
|
||||
/// </summary>
|
||||
private long GatewayAmount => DiscountCart.TotalPrice - _discountBalanceToUse;
|
||||
|
||||
private bool CanPlaceOrder => DiscountCart.Items.Count > 0 && _selectedAddress is not null;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await DiscountCart.EnsureInitializedAsync();
|
||||
|
||||
var addressTask = LoadAddresses();
|
||||
var balanceTask = LoadDiscountBalance();
|
||||
await Task.WhenAll(addressTask, balanceTask);
|
||||
|
||||
// Default: use maximum possible discount
|
||||
_discountBalanceToUse = MaxUsable;
|
||||
}
|
||||
|
||||
private async Task LoadDiscountBalance()
|
||||
{
|
||||
try
|
||||
{
|
||||
var balances = await WalletService.GetBalancesAsync();
|
||||
_discountBalance = balances.DiscountBalance;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_discountBalance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// Clamp the discount balance to use
|
||||
var discountToUse = Math.Min(_discountBalanceToUse, MaxUsable);
|
||||
|
||||
var result = await DiscountOrderService.PlaceOrderAsync(
|
||||
_selectedAddress.Id,
|
||||
discountToUse,
|
||||
_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('/');
|
||||
}
|
||||
Reference in New Issue
Block a user