Add OtpDialogService for mobile-friendly OTP authentication dialog

This commit is contained in:
masoodafar-web
2025-11-17 02:53:51 +03:30
parent a0c1452a84
commit 52b8298a18
34 changed files with 1495 additions and 279 deletions

View 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>

View 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;
}
}

View 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>

View 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);
}

View 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>
}

View 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 => "پرداخت‌شده",
_ => "در انتظار",
};
}

View 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>

View 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 => "پرداخت‌شده",
_ => "در انتظار",
};
}

View 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>
}

View 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);
}

View 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>

View 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;
}
}