Add OtpDialogService for mobile-friendly OTP authentication dialog
This commit is contained in:
80
src/FrontOffice.Main/Pages/Store/Cart.razor
Normal file
80
src/FrontOffice.Main/Pages/Store/Cart.razor
Normal file
@@ -0,0 +1,80 @@
|
||||
@attribute [Route(RouteConstants.Store.Cart)]
|
||||
@* Injection is handled in code-behind *@
|
||||
|
||||
<PageTitle>سبد خرید</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudText Typo="Typo.h4">سبد خرید</MudText>
|
||||
|
||||
@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>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||
<MudPaper Elevation="1" Class="pa-4 rounded-lg">
|
||||
<MudTable Items="CartData.Items">
|
||||
<HeaderContent>
|
||||
<MudTh>محصول</MudTh>
|
||||
<MudTh>قیمت واحد</MudTh>
|
||||
<MudTh>تعداد</MudTh>
|
||||
<MudTh>قیمت کل</MudTh>
|
||||
<MudTh></MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudAvatar Image="@context.ImageUrl" Size="Size.Medium" />
|
||||
<MudText>@context.Title</MudText>
|
||||
</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" />
|
||||
</MudTd>
|
||||
<MudTd>@FormatPrice(context.LineTotal)</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="() => Remove(context.ProductId)" />
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudPaper>
|
||||
</MudHidden>
|
||||
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp">
|
||||
<MudStack Spacing="2">
|
||||
@foreach (var item in CartData.Items)
|
||||
{
|
||||
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudAvatar Image="@item.ImageUrl" />
|
||||
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
||||
</MudStack>
|
||||
<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>
|
||||
<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)" />
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudStack>
|
||||
</MudHidden>
|
||||
|
||||
<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>
|
||||
}
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
38
src/FrontOffice.Main/Pages/Store/Cart.razor.cs
Normal file
38
src/FrontOffice.Main/Pages/Store/Cart.razor.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Store;
|
||||
|
||||
public partial class Cart : ComponentBase, IDisposable
|
||||
{
|
||||
[Inject] private CartService CartService { get; set; } = default!;
|
||||
// Navigation and Snackbar are available via _Imports.razor
|
||||
private CartService CartData => CartService;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
CartService.OnChange += StateHasChanged;
|
||||
}
|
||||
|
||||
private void ChangeQty(long productId, int value)
|
||||
{
|
||||
CartService.UpdateQuantity(productId, value);
|
||||
}
|
||||
|
||||
private void Remove(long productId)
|
||||
{
|
||||
CartService.Remove(productId);
|
||||
}
|
||||
|
||||
private void ProceedCheckout()
|
||||
{
|
||||
Navigation.NavigateTo(RouteConstants.Store.CheckoutSummary);
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CartService.OnChange -= StateHasChanged;
|
||||
}
|
||||
}
|
||||
90
src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor
Normal file
90
src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor
Normal file
@@ -0,0 +1,90 @@
|
||||
@attribute [Route(RouteConstants.Store.CheckoutSummary)]
|
||||
|
||||
<PageTitle>خلاصه خرید</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12" md="8">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg mb-3">
|
||||
<MudText Typo="Typo.h6" Class="mb-2">انتخاب آدرس</MudText>
|
||||
|
||||
@if (_loadingAddresses)
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-4">
|
||||
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
|
||||
<MudText Class="mt-2 mud-text-secondary">در حال بارگذاری آدرسها...</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
else if (_addresses.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning">
|
||||
هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید.
|
||||
</MudAlert>
|
||||
<MudButton Class="mt-2" Variant="Variant.Outlined" Href="/profile">افزودن آدرس</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Spacing="1">
|
||||
@foreach (var address in _addresses)
|
||||
{
|
||||
<MudPaper Outlined="@(_selectedAddress?.Id != address.Id)"
|
||||
Elevation="@(_selectedAddress?.Id == address.Id ? 4 : 0)"
|
||||
Class="pa-3 rounded-xl cursor-pointer"
|
||||
Style="@(_selectedAddress?.Id == address.Id ? "border: 2px solid var(--mud-palette-primary);" : "")"
|
||||
@onclick="() => _selectedAddress = address">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.subtitle2">@address.Title</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText>
|
||||
</MudStack>
|
||||
@if (address.IsDefault)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined">پیشفرض</MudChip>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
<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>
|
||||
</MudRadioGroup>
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined">در حال حاضر تنها پرداخت از طریق کیف پول فعال است.</MudAlert>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudText Typo="Typo.h6" Class="mb-2">خلاصه سفارش</MudText>
|
||||
@if (Cart.Items.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">سبد خرید شما خالی است.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudList T="CartItem" Dense="true">
|
||||
@foreach (var item in Cart.Items)
|
||||
{
|
||||
<MudListItem T="CartItem">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" Class="w-100">
|
||||
<MudText>@item.Title x @item.Quantity</MudText>
|
||||
<MudText>@FormatPrice(item.LineTotal)</MudText>
|
||||
</MudStack>
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.subtitle2">مبلغ قابل پرداخت</MudText>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Primary">@FormatPrice(Cart.Total)</MudText>
|
||||
</MudStack>
|
||||
<MudButton Disabled="@(!CanPlaceOrder)" Class="mt-3 w-100-mobile" Variant="Variant.Filled" Color="Color.Primary" OnClick="PlaceOrder" StartIcon="@Icons.Material.Filled.CheckCircle">ثبت سفارش</MudButton>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
81
src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs
Normal file
81
src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Store;
|
||||
|
||||
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!;
|
||||
// Snackbar and Navigation are injected via _Imports.razor
|
||||
|
||||
private List<GetAllUserAddressByFilterResponseModel> _addresses = new();
|
||||
private GetAllUserAddressByFilterResponseModel? _selectedAddress;
|
||||
private bool _loadingAddresses;
|
||||
|
||||
private PaymentMethod _payment = PaymentMethod.Wallet;
|
||||
|
||||
private bool CanPlaceOrder => Cart.Items.Count > 0 && _selectedAddress != null;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadAddresses();
|
||||
}
|
||||
|
||||
private async Task LoadAddresses()
|
||||
{
|
||||
_loadingAddresses = true;
|
||||
try
|
||||
{
|
||||
var response = await UserAddressContract.GetAllUserAddressByFilterAsync(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;
|
||||
}
|
||||
|
||||
// Simulate wallet payment success and create local order
|
||||
var order = new Order
|
||||
{
|
||||
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 id = await OrderService.CreateOrderAsync(order);
|
||||
Cart.Clear();
|
||||
Snackbar.Add("سفارش با موفقیت ثبت شد.", Severity.Success);
|
||||
Navigation.NavigateTo($"{RouteConstants.Store.OrderDetail}{id}");
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||
}
|
||||
79
src/FrontOffice.Main/Pages/Store/OrderDetail.razor
Normal file
79
src/FrontOffice.Main/Pages/Store/OrderDetail.razor
Normal file
@@ -0,0 +1,79 @@
|
||||
@attribute [Route(RouteConstants.Store.OrderDetail + "{id:long}")]
|
||||
@* Injection is handled in code-behind *@
|
||||
|
||||
<PageTitle>جزئیات سفارش</PageTitle>
|
||||
|
||||
@if (_loading)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
}
|
||||
else if (_order is null)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<MudAlert Severity="Severity.Warning">سفارش یافت نشد.</MudAlert>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Orders)">بازگشت</MudButton>
|
||||
</MudContainer>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<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>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||
<MudTable Items="_order.Items">
|
||||
<HeaderContent>
|
||||
<MudTh>محصول</MudTh>
|
||||
<MudTh>قیمت واحد</MudTh>
|
||||
<MudTh>تعداد</MudTh>
|
||||
<MudTh>قیمت کل</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudAvatar Image="@context.ImageUrl" Size="Size.Medium" />
|
||||
<MudText>@context.Title</MudText>
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
<MudTd>@FormatPrice(context.UnitPrice)</MudTd>
|
||||
<MudTd>@context.Quantity</MudTd>
|
||||
<MudTd>@FormatPrice(context.LineTotal)</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudHidden>
|
||||
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp">
|
||||
<MudStack Spacing="2">
|
||||
@foreach (var it in _order.Items)
|
||||
{
|
||||
<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>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Class="mud-text-secondary">@FormatPrice(it.UnitPrice) واحد</MudText>
|
||||
<MudText>تعداد: @it.Quantity</MudText>
|
||||
</MudStack>
|
||||
<MudText>جمع: @FormatPrice(it.LineTotal)</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudStack>
|
||||
</MudHidden>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudText Typo="Typo.h6" Align="Align.End">مبلغ کل: @FormatPrice(_order.Total)</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudContainer>
|
||||
}
|
||||
29
src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs
Normal file
29
src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Store;
|
||||
|
||||
public partial class OrderDetail : ComponentBase
|
||||
{
|
||||
[Inject] private OrderService OrderService { get; set; } = default!;
|
||||
|
||||
[Parameter] public long id { get; set; }
|
||||
|
||||
private Order? _order;
|
||||
private bool _loading;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_loading = true;
|
||||
_order = await OrderService.GetOrderAsync(id);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||
private static string GetStatusText(OrderStatus status) => status switch
|
||||
{
|
||||
OrderStatus.Paid => "پرداختشده",
|
||||
_ => "در انتظار",
|
||||
};
|
||||
}
|
||||
|
||||
64
src/FrontOffice.Main/Pages/Store/Orders.razor
Normal file
64
src/FrontOffice.Main/Pages/Store/Orders.razor
Normal file
@@ -0,0 +1,64 @@
|
||||
@attribute [Route(RouteConstants.Store.Orders)]
|
||||
@* Injection is handled in code-behind *@
|
||||
|
||||
<PageTitle>سفارشات من</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudText Typo="Typo.h4" Class="mb-3">سفارشات من</MudText>
|
||||
@if (_loading)
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-4">
|
||||
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
|
||||
</MudStack>
|
||||
}
|
||||
else if (_orders.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">هنوز سفارشی ثبت نکردهاید.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||
<MudTable Items="_orders" Dense="true">
|
||||
<HeaderContent>
|
||||
<MudTh>شناسه</MudTh>
|
||||
<MudTh>تاریخ</MudTh>
|
||||
<MudTh>وضعیت</MudTh>
|
||||
<MudTh>مبلغ</MudTh>
|
||||
<MudTh></MudTh>
|
||||
</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>
|
||||
<MudButton Variant="Variant.Text" Href="@($"{RouteConstants.Store.OrderDetail}{context.Id}")" StartIcon="@Icons.Material.Filled.Receipt">جزئیات</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudHidden>
|
||||
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp">
|
||||
<MudStack Spacing="2">
|
||||
@foreach (var o in _orders)
|
||||
{
|
||||
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
|
||||
<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>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText>وضعیت: @GetStatusText(o.Status)</MudText>
|
||||
<MudText Color="Color.Primary">@FormatPrice(o.Total)</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>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudStack>
|
||||
</MudHidden>
|
||||
}
|
||||
</MudContainer>
|
||||
27
src/FrontOffice.Main/Pages/Store/Orders.razor.cs
Normal file
27
src/FrontOffice.Main/Pages/Store/Orders.razor.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Store;
|
||||
|
||||
public partial class Orders : ComponentBase
|
||||
{
|
||||
[Inject] private OrderService OrderService { get; set; } = default!;
|
||||
|
||||
private List<Order> _orders = new();
|
||||
private bool _loading;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_loading = true;
|
||||
_orders = await OrderService.GetOrdersAsync();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||
private static string GetStatusText(OrderStatus status) => status switch
|
||||
{
|
||||
OrderStatus.Paid => "پرداختشده",
|
||||
_ => "در انتظار",
|
||||
};
|
||||
}
|
||||
|
||||
45
src/FrontOffice.Main/Pages/Store/ProductDetail.razor
Normal file
45
src/FrontOffice.Main/Pages/Store/ProductDetail.razor
Normal file
@@ -0,0 +1,45 @@
|
||||
@attribute [Route(RouteConstants.Store.ProductDetail + "{id:long}")]
|
||||
@* Injection is handled in code-behind *@
|
||||
|
||||
<PageTitle>جزئیات محصول</PageTitle>
|
||||
|
||||
@if (_loading)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
}
|
||||
else if (_product is null)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<MudAlert Severity="Severity.Warning">محصول یافت نشد.</MudAlert>
|
||||
<MudButton Class="mt-2" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">بازگشت</MudButton>
|
||||
</MudContainer>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Class="rounded-lg pa-2">
|
||||
<MudImage Src="@_product.ImageUrl" Alt="@_product.Title" Style="width:100%" Class="rounded-lg" />
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h4">@_product.Title</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">@_product.Description</MudText>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudText Typo="Typo.h5" Color="Color.Primary">@FormatPrice(_product.Price)</MudText>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mobile-actions-stack">
|
||||
<MudNumericField T="int" @bind-Value="_qty" Min="1" Max="20" Immediate="true" HideSpinButtons="true" Class="qty-input" />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.AddShoppingCart" OnClick="AddToCart">افزودن به سبد</MudButton>
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Secondary" OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Cart)">مشاهده سبد</MudButton>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
}
|
||||
31
src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs
Normal file
31
src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Store;
|
||||
|
||||
public partial class ProductDetail : ComponentBase
|
||||
{
|
||||
[Inject] private ProductService ProductService { get; set; } = default!;
|
||||
[Inject] private CartService Cart { get; set; } = default!;
|
||||
|
||||
[Parameter] public long id { get; set; }
|
||||
|
||||
private Product? _product;
|
||||
private bool _loading;
|
||||
private int _qty = 1;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_loading = true;
|
||||
_product = await ProductService.GetByIdAsync(id);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
private void AddToCart()
|
||||
{
|
||||
if (_product is null) return;
|
||||
Cart.Add(_product, _qty);
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||
}
|
||||
61
src/FrontOffice.Main/Pages/Store/Products.razor
Normal file
61
src/FrontOffice.Main/Pages/Store/Products.razor
Normal file
@@ -0,0 +1,61 @@
|
||||
@attribute [Route(RouteConstants.Store.Products)]
|
||||
@* Injection is handled in code-behind *@
|
||||
|
||||
<PageTitle>محصولات</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudPaper Elevation="1" Class="pa-4 mb-4 rounded-lg">
|
||||
<MudGrid Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="12" md="8">
|
||||
<MudTextField @bind-Value="_query"
|
||||
Placeholder="جستجو در محصولات..."
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
Adornment="Adornment.Start"
|
||||
Immediate="true"
|
||||
OnKeyUp="OnQueryChanged"
|
||||
Class="w-100" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="4" Class="d-flex justify-end">
|
||||
<MudButton Class="w-100-mobile" Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.ShoppingCart" Href="@RouteConstants.Store.Cart">
|
||||
سبد خرید (@Cart.Count)
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
@if (_loading)
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-6">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
||||
<MudText Class="mt-2 mud-text-secondary">در حال بارگذاری...</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
else if (_products.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">محصولی یافت نشد.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudGrid Spacing="3">
|
||||
@foreach (var p in _products)
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudCard Class="rounded-lg h-100 d-flex flex-column">
|
||||
<MudPaper Class="pa-0">
|
||||
<MudImage Src="@p.ImageUrl" Alt="@p.Title" Style="width:100%;height:160px;object-fit:cover" />
|
||||
</MudPaper>
|
||||
<MudCardContent Class="d-flex flex-column gap-2">
|
||||
<MudText Typo="Typo.h6">@p.Title</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@p.Description</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary">@FormatPrice(p.Price)</MudText>
|
||||
</MudCardContent>
|
||||
<MudCardActions Class="mt-auto d-flex justify-space-between pa-3">
|
||||
<MudButton Variant="Variant.Text" Color="Color.Secondary" Href="@($"{RouteConstants.Store.ProductDetail}{p.Id}")">جزئیات</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="() => AddToCart(p)" StartIcon="@Icons.Material.Filled.AddShoppingCart">افزودن</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
}
|
||||
</MudContainer>
|
||||
45
src/FrontOffice.Main/Pages/Store/Products.razor.cs
Normal file
45
src/FrontOffice.Main/Pages/Store/Products.razor.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Store;
|
||||
|
||||
public partial class Products : ComponentBase, IDisposable
|
||||
{
|
||||
[Inject] private ProductService ProductService { get; set; } = default!;
|
||||
[Inject] private CartService Cart { get; set; } = default!;
|
||||
|
||||
private string _query = string.Empty;
|
||||
private bool _loading;
|
||||
private List<Product> _products = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Cart.OnChange += StateHasChanged;
|
||||
await Load();
|
||||
}
|
||||
|
||||
private async Task Load()
|
||||
{
|
||||
_loading = true;
|
||||
_products = await ProductService.GetProductsAsync(_query);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
private async Task OnQueryChanged(KeyboardEventArgs _)
|
||||
{
|
||||
await Load();
|
||||
}
|
||||
|
||||
private void AddToCart(Product p)
|
||||
{
|
||||
Cart.Add(p, 1);
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Cart.OnChange -= StateHasChanged;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user