Merge branch 'main' into stage

# Conflicts:
#	src/FrontOffice.Main/Shared/MainLayout.razor
#	src/FrontOffice.Main/Utilities/WalletService.cs
This commit is contained in:
masoodafar-web
2025-11-28 05:13:28 +03:30
20 changed files with 509 additions and 216 deletions

View File

@@ -10,6 +10,7 @@ using FrontOffice.BFF.Transaction.Protobuf.Protos.Transaction;
using FrontOffice.BFF.User.Protobuf.Protos.User;
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
using FrontOffice.BFF.UserWallet.Protobuf.Protos.UserWallet;
using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart;
using FrontOffice.Main.Utilities;
@@ -76,6 +77,7 @@ public static class ConfigureServices
services.AddScoped(CreateAuthenticatedClient<UserContract.UserContractClient>);
services.AddScoped(CreateAuthenticatedClient<UserAddressContract.UserAddressContractClient>);
services.AddScoped(CreateAuthenticatedClient<UserOrderContract.UserOrderContractClient>);
services.AddScoped(CreateAuthenticatedClient<UserWalletContract.UserWalletContractClient>);
// Products gRPC
services.AddScoped(CreateAuthenticatedClient<FrontOffice.BFF.Products.Protobuf.Protos.Products.ProductsContract.ProductsContractClient>);
services.AddScoped(CreateAuthenticatedClient<TransactionContract.TransactionContractClient>);

View File

@@ -13,9 +13,10 @@
<PackageReference Include="Foursat.FrontOffice.BFF.Transaction.Protobuf" Version="0.0.111" />
<PackageReference Include="Foursat.FrontOffice.BFF.User.Protobuf" Version="0.0.116" />
<PackageReference Include="Foursat.FrontOffice.BFF.UserAddress.Protobuf" Version="0.0.114" />
<PackageReference Include="Foursat.FrontOffice.BFF.UserOrder.Protobuf" Version="0.0.112" />
<PackageReference Include="Foursat.FrontOffice.BFF.UserOrder.Protobuf" Version="0.0.114" />
<PackageReference Include="FrontOffice.BFF.Products.Protobuf" Version="0.0.11" />
<PackageReference Include="FrontOffice.BFF.ShopingCart.Protobuf" Version="0.0.12" />
<PackageReference Include="FrontOffice.BFF.ShopingCart.Protobuf" Version="0.0.15" />
<PackageReference Include="FrontOffice.BFF.UserWallet.Protobuf" Version="0.0.11" />
<PackageReference Include="MudBlazor" Version="8.14.0" />
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageReference Include="Mapster" Version="7.4.0" />

View File

@@ -18,7 +18,6 @@ public partial class Index
{
[Inject] private UserContract.UserContractClient UserContract { get; set; } = default!;
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
[Inject] private WalletService WalletService { get; set; } = default!;
private GetUserResponse _userProfile = new();
private UpdateUserRequest _updateUserRequest = new();

View File

@@ -5,7 +5,6 @@ namespace FrontOffice.Main.Pages.Profile;
public partial class Wallet : ComponentBase
{
[Inject] private WalletService WalletService { get; set; } = default!;
private (string Credit, string Network) _balances = ("-", "-");
private List<WalletTransaction> _txs = new();

View File

@@ -58,7 +58,7 @@
{
<MudStack Spacing="3" >
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h4">ویزارد ثبت‌نام</MudText>
<MudText Typo="Typo.h4">مراحل ثبت‌نام</MudText>
<MudChip T="string" Color="Color.Info" Variant="Variant.Outlined" Size="Size.Small">۳ مرحله
</MudChip>
</MudStack>

View File

@@ -10,7 +10,9 @@
@if (CartData.Items.Count == 0)
{
<MudAlert Severity="Severity.Info">سبد خرید شما خالی است.</MudAlert>
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">بازگشت به محصولات</MudButton>
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.ArrowBack"
OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">بازگشت به محصولات
</MudButton>
}
else
{
@@ -26,18 +28,49 @@
</HeaderContent>
<RowTemplate>
<MudTd>
<MudStack Spacing="1">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudAvatar Image="@context.ImageUrl" Size="Size.Medium"/>
<MudText>@context.Title</MudText>
</MudStack>
@if (context.Discount > 0 || !string.IsNullOrWhiteSpace(context.Created) || !string.IsNullOrWhiteSpace(context.Description))
{
<MudStack Spacing="1">
@if (context.Discount > 0)
{
<MudChip T="string" Color="Color.Error" Variant="Variant.Outlined"
Size="Size.Small">@($"{context.Discount}% تخفیف")</MudChip>
}
@if (!string.IsNullOrWhiteSpace(context.Description))
{
<MudText Typo="Typo.caption"
Class="mud-text-secondary">@context.Description</MudText>
}
@if (!string.IsNullOrWhiteSpace(context.Created))
{
<MudText Typo="Typo.caption" Class="mud-text-secondary">تاریخ
افزودن: @context.Created</MudText>
}
</MudStack>
}
</MudStack>
</MudTd>
<MudTd>@FormatPrice(context.UnitPrice)</MudTd>
<MudTd>
<MudNumericField T="int" Value="@context.Quantity" Min="1" Max="50" Immediate="true" HideSpinButtons="true" ValueChanged="(v) => ChangeQty(context.ProductId, v)" Style="max-width:120px" />
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudIconButton Icon="@Icons.Material.Filled.Remove"
Color="Color.Default"
OnClick="@(() => DecrementQty(context.ProductId))"/>
<MudText>@context.Quantity</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Add"
Color="Color.Default"
OnClick="@(() => IncrementQty(context.ProductId))"/>
</MudStack>
</MudTd>
<MudTd>@FormatPrice(context.LineTotal)</MudTd>
<MudTd>
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="() => Remove(context.ProductId)" />
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error"
OnClick="() => Remove(context.cartId)"/>
</MudTd>
</RowTemplate>
</MudTable>
@@ -48,33 +81,95 @@
<MudStack Spacing="2">
@foreach (var item in CartData.Items)
{
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
<MudPaper Class="pa-3 rounded-lg w-100-mobile" Outlined="true">
<MudStack Spacing="1">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudAvatar Image="@item.ImageUrl"/>
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
</MudStack>
@if (item.Discount > 0)
{
<MudChip T="string" Color="Color.Error" Variant="Variant.Outlined"
Size="Size.Small">@($"{item.Discount}% ")</MudChip>
}
</MudStack>
@if (item.Discount > 0 || !string.IsNullOrWhiteSpace(item.Created) || !string.IsNullOrWhiteSpace(item.Description))
{
<MudStack Spacing="1">
@if (!string.IsNullOrWhiteSpace(item.Description))
{
<MudText Typo="Typo.caption"
Class="mud-text-secondary">@item.Description</MudText>
}
@* @if (!string.IsNullOrWhiteSpace(item.Created)) *@
@* { *@
@* <MudText Typo="Typo.caption" Class="mud-text-secondary">تاریخ افزودن: @item.Created</MudText> *@
@* } *@
</MudStack>
}
<MudStack Spacing="1">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Class="mud-text-secondary">@FormatPrice(item.UnitPrice) واحد</MudText>
<MudNumericField T="int" Value="@item.Quantity" Min="1" Max="50" Immediate="true" HideSpinButtons="true" ValueChanged="(v) => ChangeQty(item.ProductId, v)" Class="qty-input" />
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudIconButton
Icon="@(item.Quantity <= 1 ? Icons.Material.Filled.Delete : Icons.Material.Filled.Remove)"
Color="Color.Default"
OnClick="@(() => DecrementQty(item.ProductId))"/>
<MudText>@item.Quantity</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Add"
Color="Color.Default"
OnClick="@(() => IncrementQty(item.ProductId))"/>
</MudStack>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText>جمع: @FormatPrice(item.LineTotal)</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="() => Remove(item.ProductId)" />
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
OnClick="() => Remove(item.cartId)"/>
</MudStack>
</MudStack>
</MudStack>
</MudPaper>
}
</MudStack>
</MudHidden>
@if (DeviceDetector.IsMobile())
{
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center"
Class="mobile-actions-stack px-5">
<MudText Typo="Typo.subtitle2"> تخفیف کل: <b>@FormatPrice(CartData.TotalDiscount)</b></MudText>
<MudText Typo="Typo.h5"> مبلغ کل: <b>@FormatPrice(CartData.Total)</b></MudText>
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Class="mobile-actions-stack">
<MudText Typo="Typo.h6">مبلغ قابل پرداخت: <b>@FormatPrice(CartData.Total)</b></MudText>
<MudStack Row="true" Spacing="2" Class="mobile-actions-stack">
<MudButton Variant="Variant.Text" OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">افزودن محصول</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ProceedCheckout" StartIcon="@Icons.Material.Filled.CreditCard">ادامه فرایند خرید</MudButton>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween" Spacing="2" Class="mobile-actions-stack">
<MudButton Variant="Variant.Filled" Color="Color.Primary"
OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">افزودن محصول
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Success" OnClick="ProceedCheckout"
StartIcon="@Icons.Material.Filled.CreditCard">ادامه فرایند خرید
</MudButton>
</MudStack>
}
else
{
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center"
Class="mobile-actions-stack px-5">
<MudText Typo="Typo.subtitle2"> تخفیف کل: <b>@FormatPrice(CartData.TotalDiscount)</b></MudText>
<MudText Typo="Typo.h5"> مبلغ کل: <b>@FormatPrice(CartData.Total)</b></MudText>
<MudButton Variant="Variant.Filled" Color="Color.Primary"
OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">افزودن محصول
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Success" OnClick="ProceedCheckout"
StartIcon="@Icons.Material.Filled.CreditCard">ادامه فرایند خرید
</MudButton>
</MudStack>
}
}
</MudStack>
</MudContainer>

View File

@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Components;
using FrontOffice.Main.Utilities;
using System.Linq;
namespace FrontOffice.Main.Pages.Store;
@@ -14,14 +15,28 @@ public partial class Cart : ComponentBase, IDisposable
CartService.OnChange += StateHasChanged;
}
private async Task IncrementQty(long productId)
{
var item = CartData.Items.FirstOrDefault(i => i.ProductId == productId);
if (item is null) return;
await CartService.UpdateQuantity(productId, item.Quantity + 1);
}
private async Task DecrementQty(long productId)
{
var item = CartData.Items.FirstOrDefault(i => i.ProductId == productId);
if (item is null) return;
await CartService.UpdateQuantity(productId, item.Quantity - 1);
}
private async Task ChangeQty(long productId, int value)
{
await CartService.UpdateQuantity(productId, value);
}
private async Task Remove(long productId)
private async Task Remove(long cartId)
{
await CartService.Remove(productId);
await CartService.Remove(cartId);
}
private void ProceedCheckout()
@@ -29,7 +44,7 @@ public partial class Cart : ComponentBase, IDisposable
Navigation.NavigateTo(RouteConstants.Store.CheckoutSummary);
}
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
private static string FormatPrice(long price) => string.Format("{0:N0} ", price);
public void Dispose()
{

View File

@@ -1,3 +1,4 @@
@using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder
@attribute [Route(RouteConstants.Store.CheckoutSummary)]
<PageTitle>خلاصه خرید</PageTitle>
@@ -20,7 +21,7 @@
<MudAlert Severity="Severity.Warning">
هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید.
</MudAlert>
<MudButton Class="mt-2" Variant="Variant.Outlined" Href="/profile">افزودن آدرس</MudButton>
<MudButton Class="mt-2" Variant="Variant.Outlined" Href="/profile/addresses">افزودن آدرس</MudButton>
}
else
{
@@ -51,7 +52,15 @@
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-2">روش پرداخت</MudText>
<MudRadioGroup T="PaymentMethod" @bind-SelectedOption="_payment" Color="Color.Primary">
<MudRadio T="PaymentMethod" Option="@PaymentMethod.Wallet" Color="Color.Primary">کیف پول</MudRadio>
<MudRadio T="PaymentMethod" Option="@PaymentMethod.Wallet" Color="Color.Primary">
@* <MudText></MudText> *@
@* <MudText>20000</MudText> *@
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.subtitle1">کیف پول <b></b></MudText>
<MudText Typo="Typo.subtitle2"> @(FormatPrice(walletBalance)) <b></b></MudText>
</MudStack>
</MudRadio>
</MudRadioGroup>
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined">در حال حاضر تنها پرداخت از طریق کیف پول فعال است.</MudAlert>
</MudPaper>
@@ -70,10 +79,16 @@
@foreach (var item in Cart.Items)
{
<MudListItem T="CartItem">
<MudStack Row="true" Justify="Justify.SpaceBetween" Class="w-100">
<MudStack Spacing="1" Class="w-100">
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText>@item.Title x @item.Quantity</MudText>
<MudText>@FormatPrice(item.LineTotal)</MudText>
</MudStack>
@if (item.Discount > 0)
{
<MudText Typo="Typo.caption" Class="mud-text-secondary">@($"تخفیف: {item.Discount}%")</MudText>
}
</MudStack>
</MudListItem>
}
</MudList>

View File

@@ -1,4 +1,5 @@
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using MudBlazor;
@@ -10,11 +11,13 @@ public partial class CheckoutSummary : ComponentBase
[Inject] private CartService Cart { get; set; } = default!;
[Inject] private OrderService OrderService { get; set; } = default!;
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
[Inject] private UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
// Snackbar and Navigation are injected via _Imports.razor
private List<GetAllUserAddressByFilterResponseModel> _addresses = new();
private GetAllUserAddressByFilterResponseModel? _selectedAddress;
private bool _loadingAddresses;
private long walletBalance;
private PaymentMethod _payment = PaymentMethod.Wallet;
@@ -23,6 +26,13 @@ public partial class CheckoutSummary : ComponentBase
protected override async Task OnInitializedAsync()
{
await LoadAddresses();
await LoadWalletBalance();
}
private async Task LoadWalletBalance()
{
var walletResult = await WalletService.GetBalancesAsync();
walletBalance = walletResult.CreditBalance + walletResult.NetworkBalance;
}
private async Task LoadAddresses()
@@ -61,20 +71,22 @@ public partial class CheckoutSummary : ComponentBase
return;
}
// Simulate wallet payment success and create local order
var order = new Order
try
{
Status = OrderStatus.Paid,
PaymentMethod = _payment,
AddressId = _selectedAddress.Id,
AddressSummary = _selectedAddress.Address,
Items = Cart.Items.Select(i => new OrderItem(i.ProductId, i.Title, i.ImageUrl, i.UnitPrice, i.Quantity)).ToList()
var request = new SubmitShopBuyOrderRequest
{
TotalAmount = Cart.Total
};
var id = await OrderService.CreateOrderAsync(order);
var response = await UserOrderContract.SubmitShopBuyOrderAsync(request);
await Cart.Clear();
Snackbar.Add("سفارش با موفقیت ثبت شد.", Severity.Success);
Navigation.NavigateTo($"{RouteConstants.Store.OrderDetail}{id}");
Navigation.NavigateTo($"{RouteConstants.Store.OrderDetail}{response.Id}");
}
catch (Exception ex)
{
Snackbar.Add($"خطا در ثبت سفارش: {ex.Message}", Severity.Error);
}
}
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);

View File

@@ -24,13 +24,13 @@ else
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudStack Spacing="2">
<MudText Typo="Typo.h5">سفارش #@_order.Id</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">تاریخ: @_order.CreatedAt.ToString("yyyy/MM/dd HH:mm")</MudText>
<MudText Typo="Typo.body2">وضعیت: @GetStatusText(_order.Status)</MudText>
<MudText Typo="Typo.body2">روش پرداخت: کیف پول</MudText>
<MudText Typo="Typo.body2">آدرس: @_order.AddressSummary</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">تاریخ: @_order.PaymentDate.ToDateTime().MiladiToJalaliWithTime()</MudText>
<MudText Typo="Typo.body2">وضعیت: @GetStatusText(_order.PaymentStatus)</MudText>
<MudText Typo="Typo.body2">روش پرداخت: @GetPaymentMethodText(_order.PaymentMethod)</MudText>
<MudText Typo="Typo.body2">آدرس: @_order.UserAddressText</MudText>
<MudDivider Class="my-2" />
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
<MudTable Items="_order.Items">
<MudTable Items="_order.FactorDetails">
<HeaderContent>
<MudTh>محصول</MudTh>
<MudTh>قیمت واحد</MudTh>
@@ -40,39 +40,39 @@ else
<RowTemplate>
<MudTd>
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudAvatar Image="@context.ImageUrl" Size="Size.Medium" />
<MudText>@context.Title</MudText>
<MudAvatar Image="@context.ProductThumbnailPath" Size="Size.Medium" />
<MudText>@context.ProductTitle</MudText>
</MudStack>
</MudTd>
<MudTd>@FormatPrice(context.UnitPrice)</MudTd>
<MudTd>@context.Quantity</MudTd>
<MudTd>@FormatPrice(context.LineTotal)</MudTd>
<MudTd>@FormatPrice(context.UnitPrice.Value)</MudTd>
<MudTd>@context.Count</MudTd>
<MudTd>@FormatPrice(context.UnitPrice.Value*context.Count.Value)</MudTd>
</RowTemplate>
</MudTable>
</MudHidden>
<MudHidden Breakpoint="Breakpoint.MdAndUp">
<MudStack Spacing="2">
@foreach (var it in _order.Items)
@foreach (var it in _order.FactorDetails)
{
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
<MudStack Spacing="1">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudAvatar Image="@it.ImageUrl" />
<MudText>@it.Title</MudText>
<MudAvatar Image="@it.ProductThumbnailPath" />
<MudText>@it.ProductTitle</MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Class="mud-text-secondary">@FormatPrice(it.UnitPrice) واحد</MudText>
<MudText>تعداد: @it.Quantity</MudText>
<MudText Class="mud-text-secondary">@FormatPrice(it.UnitPrice.Value) واحد</MudText>
<MudText>تعداد: @it.Count</MudText>
</MudStack>
<MudText>جمع: @FormatPrice(it.LineTotal)</MudText>
<MudText>جمع: @FormatPrice(it.UnitPrice.Value*it.Count.Value)</MudText>
</MudStack>
</MudPaper>
}
</MudStack>
</MudHidden>
<MudDivider Class="my-2" />
<MudText Typo="Typo.h6" Align="Align.End">مبلغ کل: @FormatPrice(_order.Total)</MudText>
<MudText Typo="Typo.h6" Align="Align.End">مبلغ کل: @FormatPrice(_order.FactorDetails.Sum(s=>s.UnitPrice.Value*s.Count.Value))</MudText>
</MudStack>
</MudPaper>
</MudContainer>

View File

@@ -1,3 +1,4 @@
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
@@ -9,7 +10,7 @@ public partial class OrderDetail : ComponentBase
[Parameter] public long id { get; set; }
private Order? _order;
private GetUserOrderResponse? _order;
private bool _loading;
protected override async Task OnParametersSetAsync()
@@ -20,10 +21,26 @@ public partial class OrderDetail : ComponentBase
}
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
private static string GetStatusText(OrderStatus status) => status switch
private string GetStatusText(PaymentStatus orderPaymentStatus)
{
OrderStatus.Paid => "پرداخت‌شده",
_ => "در انتظار",
return orderPaymentStatus switch
{
PaymentStatus.Pending => "در انتظار پرداخت",
PaymentStatus.Success => "پرداخت شده",
PaymentStatus.Reject => "پرداخت ناموفق",
_ => "نامشخص",
};
}
private string GetPaymentMethodText(PaymentMethod orderPaymentMethod)
{
return orderPaymentMethod switch
{
PaymentMethod.Wallet => "کیف پول",
PaymentMethod.Ipg => "درگاه پرداخت اینترنتی",
_ => "نامشخص",
};
}
}

View File

@@ -28,11 +28,11 @@
</HeaderContent>
<RowTemplate>
<MudTd>@context.Id</MudTd>
<MudTd>@context.CreatedAt.ToString("yyyy/MM/dd HH:mm")</MudTd>
<MudTd>@GetStatusText(context.Status)</MudTd>
<MudTd>@FormatPrice(context.Total)</MudTd>
<MudTd>@context.PaymentDate.ToDateTime().MiladiToJalaliWithTime()</MudTd>
<MudTd>@GetStatusText(context.PaymentStatus)</MudTd>
<MudTd>@FormatPrice(context.FactorDetails.Sum(s=>s.UnitPrice.Value*s.Count.Value))</MudTd>
<MudTd>
<MudButton Variant="Variant.Text" Href="@($"{RouteConstants.Store.OrderDetail}{context.Id}")" StartIcon="@Icons.Material.Filled.Receipt">جزئیات</MudButton>
<MudButton Variant="Variant.Text" Href="@($"{RouteConstants.Store.OrderDetail}{context}")" StartIcon="@Icons.Material.Filled.Receipt">جزئیات</MudButton>
</MudTd>
</RowTemplate>
</MudTable>
@@ -46,11 +46,11 @@
<MudStack Spacing="1">
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText>سفارش #@o.Id</MudText>
<MudText Class="mud-text-secondary">@o.CreatedAt.ToString("yy/MM/dd HH:mm")</MudText>
<MudText Class="mud-text-secondary">@o.PaymentDate.ToDateTime().MiladiToJalaliWithTime()</MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText>وضعیت: @GetStatusText(o.Status)</MudText>
<MudText Color="Color.Primary">@FormatPrice(o.Total)</MudText>
<MudText>وضعیت: @GetStatusText(o.PaymentStatus)</MudText>
<MudText Color="Color.Primary">@FormatPrice(o.FactorDetails.Sum(s=>s.UnitPrice.Value*s.Count.Value)))</MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.FlexEnd">
<MudButton Size="Size.Small" Variant="Variant.Outlined" Href="@($"{RouteConstants.Store.OrderDetail}{o.Id}")" StartIcon="@Icons.Material.Filled.Receipt">جزئیات</MudButton>

View File

@@ -1,3 +1,4 @@
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
@@ -7,7 +8,7 @@ public partial class Orders : ComponentBase
{
[Inject] private OrderService OrderService { get; set; } = default!;
private List<Order> _orders = new();
private List<GetUserOrderResponse> _orders = new();
private bool _loading;
protected override async Task OnInitializedAsync()
@@ -18,10 +19,17 @@ public partial class Orders : ComponentBase
}
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
private static string GetStatusText(OrderStatus status) => status switch
private string GetStatusText(PaymentStatus contextPaymentStatus)
{
OrderStatus.Paid => "پرداخت‌شده",
_ => "در انتظار",
return contextPaymentStatus switch
{
PaymentStatus.Pending => "در انتظار پرداخت",
PaymentStatus.Success => "پرداخت شده",
PaymentStatus.Reject => "پرداخت ناموفق",
_ => "نامشخص",
};
}
}

View File

@@ -35,29 +35,41 @@
</div>
<div class="d-none d-md-flex align-center gap-10">
@* @if (_isAuthenticated && false) *@
@* { *@
@* <MudLink Href="@(RouteConstants.Store.Products)" Typo="Typo.subtitle1" Class="mud-link">محصولات</MudLink> *@
@* <MudLink Href="@(RouteConstants.Store.Cart)" Typo="Typo.subtitle1" Class="mud-link">سبد خرید</MudLink> *@
@* <MudLink Href="@(RouteConstants.Store.Orders)" Typo="Typo.subtitle1" Class="mud-link">سفارشات من</MudLink> *@
@* } *@
<MudLink Href="@(RouteConstants.FAQ.Index)" Typo="Typo.subtitle1" Class="mud-link">سوالات متداول</MudLink>
<MudLink Href="@(RouteConstants.Contact.Index)" Typo="Typo.subtitle1" Class="mud-link">ارتباط با ما</MudLink>
<MudLink Href="@(RouteConstants.About.Index)" Typo="Typo.subtitle1" Class="mud-link">درباره ما</MudLink>
@if (_isAuthenticated)
{
<MudLink Href="@(RouteConstants.Store.Products)" Typo="Typo.subtitle1" Class="mud-link">
محصولات
</MudLink>
<MudLink Href="@(RouteConstants.Store.Cart)" Typo="Typo.subtitle1" Class="mud-link">سبد خرید
</MudLink>
<MudLink Href="@(RouteConstants.Store.Orders)" Typo="Typo.subtitle1" Class="mud-link">سفارشات
من
</MudLink>
}
<MudLink Href="@(RouteConstants.FAQ.Index)" Typo="Typo.subtitle1" Class="mud-link">سوالات متداول
</MudLink>
<MudLink Href="@(RouteConstants.Contact.Index)" Typo="Typo.subtitle1" Class="mud-link">ارتباط با
ما
</MudLink>
<MudLink Href="@(RouteConstants.About.Index)" Typo="Typo.subtitle1" Class="mud-link">درباره ما
</MudLink>
</div>
<div class="d-flex align-center gap-2">
@if (_isAuthenticated)
{
<MudMenu Icon="@Icons.Material.Filled.Person" Color="Color.Inherit" Size="Size.Medium">
<MudMenuItem OnClick="NavigateToProfile" Disabled="@(!AuthService.IsCompleteRegister())">پروفایل</MudMenuItem>
<MudMenuItem OnClick="NavigateToProfile" Disabled="@(!AuthService.IsCompleteRegister())">
پروفایل
</MudMenuItem>
<MudDivider/>
<MudMenuItem OnClick="Logout">خروج از حساب</MudMenuItem>
</MudMenu>
}
else
{
<MudIconButton Icon="@Icons.Material.Filled.Login" Color="Color.Inherit" OnClick="OpenAuthDialog" />
<MudIconButton Icon="@Icons.Material.Filled.Login" Color="Color.Inherit"
OnClick="OpenAuthDialog"/>
}
<MudIconButton OnClick="@ToggleTheme" Edge="Edge.End"
@@ -67,34 +79,54 @@
</MudAppBar>
<!-- Mobile Drawer -->
<MudDrawer @bind-Open="_drawerOpen" Anchor="Anchor.Right" Elevation="1" Variant="DrawerVariant.Temporary" Class="pa-4">
<MudDrawer @bind-Open="_drawerOpen" Anchor="Anchor.Right" Elevation="1" Variant="DrawerVariant.Temporary"
Class="pa-4">
<MudStack Spacing="2">
@* @if (_isAuthenticated) *@
@* { *@
@* <MudLink Href="@(RouteConstants.Store.Products)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">محصولات</MudLink> *@
@* <MudLink Href="@(RouteConstants.Store.Cart)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">سبد خرید</MudLink> *@
@* <MudLink Href="@(RouteConstants.Store.Orders)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">سفارشات من</MudLink> *@
@* } *@
<MudLink Href="@(RouteConstants.FAQ.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">سوالات متداول</MudLink>
<MudLink Href="@(RouteConstants.About.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">درباره ما</MudLink>
<MudLink Href="@(RouteConstants.Contact.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">ارتباط با ما</MudLink>
@if (_isAuthenticated)
{
<MudLink Href="@(RouteConstants.Store.Products)" Typo="Typo.subtitle1"
OnClick="() => _drawerOpen=false">محصولات
</MudLink>
<MudLink Href="@(RouteConstants.Store.Cart)" Typo="Typo.subtitle1"
OnClick="() => _drawerOpen=false">سبد خرید
</MudLink>
<MudLink Href="@(RouteConstants.Store.Orders)" Typo="Typo.subtitle1"
OnClick="() => _drawerOpen=false">سفارشات من
</MudLink>
}
<MudLink Href="@(RouteConstants.FAQ.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">
سوالات متداول
</MudLink>
<MudLink Href="@(RouteConstants.About.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">
درباره ما
</MudLink>
<MudLink Href="@(RouteConstants.Contact.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">
ارتباط با ما
</MudLink>
<MudDivider Class="my-2"/>
@if (_isAuthenticated)
{
<MudButton Href="/profile" Color="Color.Primary" OnClick="() => _drawerOpen=false">پروفایل</MudButton>
<MudButton Variant="Variant.Text" Color="Color.Error" OnClick="() => { _drawerOpen=false; Logout(); }">خروج از حساب</MudButton>
<MudButton Href="/profile" Color="Color.Primary" OnClick="() => _drawerOpen=false">پروفایل
</MudButton>
<MudButton Variant="Variant.Text" Color="Color.Error"
OnClick="() => { _drawerOpen=false; Logout(); }">خروج از حساب
</MudButton>
}
else
{
<MudButton Color="Color.Primary" OnClick="() => { _drawerOpen=false; OpenAuthDialog(); }">ورود</MudButton>
<MudButton Color="Color.Primary" OnClick="() => { _drawerOpen=false; OpenAuthDialog(); }">ورود
</MudButton>
}
</MudStack>
</MudDrawer>
<MudMainContent>
@Body
@if (DeviceDetector.IsDesktop())
{
<Footer/>
}
</MudMainContent>
</MudLayout>

View File

@@ -1,11 +1,16 @@
using DateTimeConverterCL;
using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart;
using Google.Protobuf.WellKnownTypes;
namespace FrontOffice.Main.Utilities;
public record CartItem(long ProductId, string Title, string ImageUrl, long UnitPrice, int Quantity)
public record CartItem(long cartId,long ProductId, string Title, string ImageUrl, long UnitPrice, int Quantity)
{
public long LineTotal => UnitPrice * Quantity;
public long DiscountValue => (Discount*(UnitPrice * Quantity))/100;
public int Discount { get; init; }
public string Created { get; init; } = string.Empty;
public string Description { get; init; } = string.Empty;
}
public class CartService
@@ -22,30 +27,52 @@ public class CartService
public IReadOnlyList<CartItem> Items => _items.AsReadOnly();
public long Total => _items.Sum(i => i.LineTotal);
public long TotalDiscount => _items.Sum(i => i.DiscountValue);
public int Count => _items.Sum(i => i.Quantity);
public async Task Add(Product product, int quantity = 1)
{
if (quantity <= 0) return;
var existing = _items.FirstOrDefault(i => i.ProductId == product.Id);
int newQuantity;
if (existing is null)
{
_items.Add(new CartItem(product.Id, product.Title, product.ImageUrl, product.Price, quantity));
newQuantity = quantity;
_items.Add(new CartItem(0,product.Id, product.Title, product.ImageUrl, product.Price, newQuantity)
{
Discount = product.Discount,
Created = DateTime.Now.MiladiToJalali(),
Description = product.Description
});
}
else
{
var idx = _items.IndexOf(existing);
_items[idx] = existing with { Quantity = existing.Quantity + quantity };
newQuantity = existing.Quantity + quantity;
_items[idx] = existing with { Quantity = newQuantity };
}
Notify();
try
{
if (existing is null)
{
await _client.AddNewUserCartAsync(new AddNewUserCartRequest
{
ProductId = product.Id,
Count = quantity
Count = newQuantity
});
await LoadFromServerAsync();
}
else
{
await _client.UpdateUserCartAsync(new UpdateUserCartRequest
{
UserCartId = existing.cartId,
Count = newQuantity
});
}
}
catch
{
@@ -59,7 +86,7 @@ public class CartService
if (existing is null) return;
if (quantity <= 0)
{
Remove(productId);
Remove(existing.cartId);
return;
}
var idx = _items.IndexOf(existing);
@@ -70,7 +97,7 @@ public class CartService
{
await _client.UpdateUserCartAsync(new UpdateUserCartRequest
{
UserCartId = productId,
UserCartId = existing.cartId,
Count = quantity
});
}
@@ -80,9 +107,9 @@ public class CartService
}
}
public async Task Remove(long productId)
public async Task Remove(long cartId)
{
_items.RemoveAll(i => i.ProductId == productId);
_items.RemoveAll(i => i.cartId == cartId);
Notify();
try
@@ -90,7 +117,7 @@ public class CartService
// Interpret Count=0 as remove from cart
await _client.UpdateUserCartAsync(new UpdateUserCartRequest
{
UserCartId = productId,
UserCartId = cartId,
Count = 0
});
}
@@ -131,14 +158,20 @@ public class CartService
{
var response = await _client.GetAllUserCartAsync(new Empty());
_items.Clear();
foreach (var m in response.Models)
foreach (var model in response.Models)
{
var item = new CartItem(
ProductId: m.Id,
Title: m.Title ?? string.Empty,
ImageUrl: string.IsNullOrWhiteSpace(m.ImagePath) ? string.Empty : UrlUtility.DownloadUrl + m.ImagePath,
UnitPrice: m.Price,
Quantity: m.SaleCount > 0 ? m.SaleCount : 1);
cartId:model.Id,
ProductId: model.ProductId,
Title: model.ProductTitle ?? string.Empty,
ImageUrl: string.IsNullOrWhiteSpace(model.ProductThumbnailPath) ? string.Empty : UrlUtility.DownloadUrl + model.ProductThumbnailPath,
UnitPrice: model.ProductPrice,
Quantity: model.Count > 0 ? model.Count : 1)
{
Discount = model.ProductDiscount,
Created = model.Created.ToDateTime().MiladiToJalali(),
Description = model.ProductShortInfomation
};
_items.Add(item);
}
Notify();

View File

@@ -1,47 +1,64 @@
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
namespace FrontOffice.Main.Utilities;
public enum PaymentMethod
{
Wallet = 1,
}
// public enum PaymentMethod
// {
// Wallet = 1,
// }
//
// public enum OrderStatus
// {
// Pending = 0,
// Paid = 1,
// }
public enum OrderStatus
{
Pending = 0,
Paid = 1,
}
public record OrderItem(long ProductId, string Title, string ImageUrl, long UnitPrice, int Quantity)
{
public long LineTotal => UnitPrice * Quantity;
}
public class Order
{
public long Id { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
public OrderStatus Status { get; set; } = OrderStatus.Pending;
public PaymentMethod PaymentMethod { get; set; } = PaymentMethod.Wallet;
public long AddressId { get; set; }
public string AddressSummary { get; set; } = string.Empty;
public List<OrderItem> Items { get; set; } = new();
public long Total => Items.Sum(i => i.LineTotal);
}
// public record OrderItem(long ProductId, string Title, string ImageUrl, long UnitPrice, int Quantity)
// {
// public long LineTotal => UnitPrice * Quantity;
// }
//
// public class Order
// {
// public long Id { get; set; }
// public DateTime CreatedAt { get; set; } = DateTime.Now;
// public OrderStatus Status { get; set; } = OrderStatus.Pending;
// public PaymentMethod PaymentMethod { get; set; } = PaymentMethod.Wallet;
// public long AddressId { get; set; }
// public string AddressSummary { get; set; } = string.Empty;
// public List<OrderItem> Items { get; set; } = new();
// public long Total => Items.Sum(i => i.LineTotal);
// }
public class OrderService
{
private long _seq = 1000;
private readonly List<Order> _orders = new();
private readonly List<GetUserOrderResponse> _orders = new();
private readonly UserOrderContract.UserOrderContractClient _userOrderContractClient;
public Task<List<Order>> GetOrdersAsync() => Task.FromResult(_orders.OrderByDescending(o => o.CreatedAt).ToList());
public Task<Order?> GetOrderAsync(long id) => Task.FromResult(_orders.FirstOrDefault(o => o.Id == id));
public Task<long> CreateOrderAsync(Order order)
public OrderService(UserOrderContract.UserOrderContractClient userOrderContractClient)
{
order.Id = Interlocked.Increment(ref _seq);
_orders.Add(order);
return Task.FromResult(order.Id);
}
_userOrderContractClient = userOrderContractClient;
}
public Task<List<GetUserOrderResponse>> GetOrdersAsync()
{
return Task.FromResult(_orders.OrderByDescending(o => o.PaymentDate).ToList());
}
public async Task<GetUserOrderResponse?> GetOrderAsync(long id)
{
var order = _orders.FirstOrDefault(o => o.Id == id);
if (order == null)
{
var result = await _userOrderContractClient.GetUserOrderAsync(new GetUserOrderRequest()
{
Id = id
});
_orders.Add(result);
return result;
}
return order;
}
}

View File

@@ -3,7 +3,15 @@ using FrontOffice.BFF.Products.Protobuf.Protos.Products;
namespace FrontOffice.Main.Utilities;
public record Product(long Id, string Title, string Description, string ImageUrl, long Price);
public record Product(
long Id,
string Title,
string Description,
string ImageUrl,
long Price,
int Discount = 0,
int Rate = 0,
int RemainingCount = 0);
public class ProductService
{
@@ -81,7 +89,10 @@ public class ProductService
Title: m.Title ?? string.Empty,
Description: m.Description ?? string.Empty,
ImageUrl: string.IsNullOrWhiteSpace(m.ImagePath) ? string.Empty : UrlUtility.DownloadUrl + m.ImagePath,
Price: m.Price
Price: m.Price,
Discount: m.Discount,
Rate: m.Rate,
RemainingCount: m.RemainingCount
);
_cache[p.Id] = p;
list.Add(p);

View File

@@ -1,3 +1,6 @@
using FrontOffice.BFF.UserWallet.Protobuf.Protos.UserWallet;
using Google.Protobuf.WellKnownTypes;
namespace FrontOffice.Main.Utilities;
public record WalletBalances(long CreditBalance, long NetworkBalance);
@@ -5,16 +8,48 @@ public record WalletTransaction(DateTime Date, long Amount, string Channel, stri
public class WalletService
{
private WalletBalances _balances = new(0, 0);
private readonly List<WalletTransaction> _transactions = new()
{
// new(DateTime.Now.AddDays(-1), 500_000, "درگاه بانکی", "شارژ کیف پول"),
// new(DateTime.Now.AddDays(-2), 200_000, "شبکه/معرف", "پاداش شبکه"),
// new(DateTime.Now.AddDays(-4), -120_000, "خرید", "برداشت بابت سفارش #1452"),
// new(DateTime.Now.AddDays(-9), 900_000, "کیف پول شرکای تجاری", "اعتبار خرید"),
};
private readonly UserWalletContract.UserWalletContractClient _client;
public Task<WalletBalances> GetBalancesAsync() => Task.FromResult(_balances);
public Task<List<WalletTransaction>> GetTransactionsAsync() => Task.FromResult(_transactions.OrderByDescending(t => t.Date).ToList());
public WalletService(UserWalletContract.UserWalletContractClient client)
{
_client = client;
}
public async Task<WalletBalances> GetBalancesAsync()
{
try
{
var response = await _client.GetUserWalletAsync(new Empty());
return new WalletBalances(response.Balance, response.NetworkBalance);
}
catch
{
// Fallback to mock data if backend is unavailable
return new WalletBalances(350_000, 1_250_000);
}
}
public async Task<List<WalletTransaction>> GetTransactionsAsync()
{
try
{
var response = await _client.GetAllUserWalletChangeLogAsync(new Empty());
return response.Models.Select(t => new WalletTransaction(DateTime.Now, t.CurrentBalance,t.RefrenceId.Value.ToString(),t.IsIncrease?"شارژ کیف پول":"برداشت از کیف پول")
).ToList();
}
catch
{
// Fallback to mock data if backend is unavailable
var _transactions = new List<WalletTransaction>
{
new(DateTime.Now.AddDays(-1), 500_000, "درگاه بانکی", "شارژ کیف پول"),
new(DateTime.Now.AddDays(-2), 200_000, "شبکه/معرف", "پاداش شبکه"),
new(DateTime.Now.AddDays(-4), -120_000, "خرید", "برداشت بابت سفارش #1452"),
new(DateTime.Now.AddDays(-9), 900_000, "کیف پول شرکای تجاری", "اعتبار خرید"),
};
return _transactions.OrderByDescending(t => t.Date).ToList();
}
}
}

View File

@@ -18,3 +18,5 @@
@inject IJSRuntime JSRuntime
@inject ISnackbar Snackbar
@inject MainService MainService
@inject IDeviceDetector DeviceDetector
@inject WalletService WalletService

View File

@@ -1,7 +1,7 @@
نمونه قرارداد همکاری
---------------------
این فایل صرفاً نمونه‌ای برای نمایش قابلیت دانلود قرارداد در ویزارد ثبت‌نام است.
این فایل صرفاً نمونه‌ای برای نمایش قابلیت دانلود قرارداد در مراحل ثبت‌نام است.
بند ۱- اطلاعات شخص حقیقی: شامل نام، نام خانوادگی، کد ملی و شماره تماس متقاضی است.
بند ۲- تعهدات طرفین: متقاضی متعهد می‌شود قوانین حاکم بر پلتفرم را رعایت نموده و اطلاعات صحیح ارائه نماید.