feat: full FrontOffice updates - discount store, blog, payment gateway, UI improvements
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:
masoodafar-web
2026-02-16 00:51:39 +03:30
parent f0d1156457
commit 6db83ccd90
104 changed files with 7668 additions and 1370 deletions
+9 -9
View File
@@ -5,7 +5,7 @@
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
<MudStack Spacing="3">
<MudText Typo="Typo.h4">سبد خرید</MudText>
<PageHeader Title="سبد خرید" BackHref="@RouteConstants.Store.Products" />
@if (CartData.Items.Count == 0)
{
@@ -30,8 +30,8 @@
<MudTd>
<MudStack Spacing="1">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudImage Src="@GetProductImageUrl(context.ImageUrl)" Alt="@context.Title"
Width="64" Height="64" Class="product-thumb" />
<AppImage Path="@GetProductImageUrl(context.ImageUrl)" Alt="@context.Title"
ImgWidth="64" ImgHeight="64" Class="product-thumb" />
<MudText>@context.Title</MudText>
</MudStack>
@if (context.Discount > 0 || !string.IsNullOrWhiteSpace(context.Created) || !string.IsNullOrWhiteSpace(context.Description))
@@ -86,8 +86,8 @@
<MudStack Spacing="1">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudImage Src="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
Width="50" Height="50" Class="rounded-circle" />
<AppImage Path="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
ImgWidth="50" ImgHeight="50" Class="rounded-circle" />
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
</MudStack>
@if (item.Discount > 0)
@@ -154,8 +154,8 @@
</MudStack>
}
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h6" Style="font-weight:bold;">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.h6" Color="Color.Primary" Style="font-weight:bold;">@FormatPrice(TotalWithVAT) تومان</MudText>
<MudText Typo="Typo.h6" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.h6" Color="Color.Primary" Class="fw-bold">@FormatPrice(TotalWithVAT) تومان</MudText>
</MudStack>
</MudStack>
@@ -185,8 +185,8 @@
}
<MudDivider Class="my-1" />
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.subtitle1" Style="font-weight:bold;">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight:bold;">@FormatPrice(TotalWithVAT) تومان</MudText>
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(TotalWithVAT) تومان</MudText>
</MudStack>
</MudStack>
</MudPaper>
@@ -66,7 +66,7 @@ public partial class Cart : ComponentBase, IDisposable
private long VATAmount => VAT.CalculateVAT(CartData.Total);
private static string GetProductImageUrl(string? imageUrl)
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl;
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
public void Dispose()
{
@@ -5,9 +5,9 @@
<PageTitle>دسته‌بندی‌ها</PageTitle>
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
<PageHeader Title="دسته‌بندی محصولات" BackHref="@RouteConstants.Store.Products" />
<MudPaper Elevation="1" Class="pa-4 mb-4 rounded-lg">
<MudStack Spacing="1">
<MudText Typo="Typo.h5">دسته‌بندی محصولات</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">
از بین درخت دسته‌بندی‌ها انتخاب کنید تا محصولات آن دسته را مشاهده نمایید.
</MudText>
@@ -5,6 +5,7 @@
<PageTitle>خلاصه خرید</PageTitle>
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
<PageHeader Title="خلاصه خرید" BackHref="@RouteConstants.Store.Cart" />
<MudGrid Spacing="3">
<MudItem xs="12" md="8">
<MudPaper Elevation="2" Class="pa-4 rounded-lg mb-3">
@@ -30,7 +31,7 @@
@foreach (var address in _addresses)
{
<MudPaper Outlined="@(_selectedAddress?.Id != address.Id)"
Elevation="@(_selectedAddress?.Id == address.Id ? 4 : 0)"
Elevation="@(_selectedAddress?.Id == address.Id ? 2 : 0)"
Class="pa-3 rounded-xl cursor-pointer"
Style="@(_selectedAddress?.Id == address.Id ? "border: 2px solid var(--mud-palette-primary);" : "")"
@onclick="() => _selectedAddress = address">
@@ -84,8 +85,8 @@
<MudListItemText>
<MudStack Spacing="1">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudImage Src="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
Width="48" Height="48" Class="rounded-circle" />
<AppImage Path="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
ImgWidth="48" ImgHeight="48" Class="rounded-circle" />
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
<MudText Typo="Typo.subtitle2">@FormatPrice(item.LineTotal)</MudText>
</MudStack>
@@ -118,8 +119,8 @@
}
<MudDivider Class="my-1" />
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(VAT.AddVAT(Cart.Total))</MudText>
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(VAT.AddVAT(Cart.Total))</MudText>
</MudStack>
</MudStack>
@@ -113,5 +113,5 @@ public partial class CheckoutSummary : ComponentBase
private long CalculateVAT() => VAT.CalculateVAT(Cart.Total);
private static string GetProductImageUrl(string? imageUrl)
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl;
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
}
@@ -6,24 +6,24 @@
@if (_loading)
{
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
<MudStack AlignItems="AlignItems.Center">
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
</MudStack>
<LoadingState />
</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>
<EmptyState Icon="@Icons.Material.Filled.SearchOff"
Title="سفارش یافت نشد."
ActionText="بازگشت"
ActionHref="@RouteConstants.Store.Orders" />
</MudContainer>
}
else
{
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
<PageHeader Title="@($"جزئیات سفارش #{_order.Id}")" BackHref="@RouteConstants.Store.Orders" />
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudStack Spacing="2">
<MudText Typo="Typo.h5">سفارش #@_order.Id</MudText>
@if (_order.PaymentDate != null)
{
<MudText Typo="Typo.body2" Class="mud-text-secondary">تاریخ پرداخت: @_order.PaymentDate.ToDateTime().MiladiToJalaliWithTime()</MudText>
@@ -46,8 +46,8 @@ else
<RowTemplate>
<MudTd>
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudImage Src="@GetProductImageUrl(context.ProductThumbnailPath)" Alt="@context.ProductTitle"
Width="60" Height="60" Class="rounded-circle" />
<AppImage Path="@GetProductImageUrl(context.ProductThumbnailPath)" Alt="@context.ProductTitle"
ImgWidth="60" ImgHeight="60" Class="rounded-circle" />
<MudText>@context.ProductTitle</MudText>
</MudStack>
</MudTd>
@@ -65,8 +65,8 @@ else
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
<MudStack Spacing="1">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudImage Src="@GetProductImageUrl(it.ProductThumbnailPath)" Alt="@it.ProductTitle"
Width="60" Height="60" Class="rounded-circle" />
<AppImage Path="@GetProductImageUrl(it.ProductThumbnailPath)" Alt="@it.ProductTitle"
ImgWidth="60" ImgHeight="60" Class="rounded-circle" />
<MudText>@it.ProductTitle</MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
@@ -100,15 +100,15 @@ else
</MudStack>
<MudDivider Class="my-1" />
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(_order.VatInfo.TotalAmount)</MudText>
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(_order.VatInfo.TotalAmount)</MudText>
</MudStack>
}
else
{
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(subtotal)</MudText>
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(subtotal)</MudText>
</MudStack>
}
</MudStack>
@@ -45,6 +45,6 @@ public partial class OrderDetail : ComponentBase
}
private static string GetProductImageUrl(string? imageUrl)
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : UrlUtility.DownloadUrl+imageUrl;
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
}
@@ -4,6 +4,7 @@
<PageTitle>پیگیری سفارش</PageTitle>
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
<PageHeader Title="پیگیری سفارش" BackHref="@RouteConstants.Store.Orders" />
@if (_loading)
{
<MudStack AlignItems="AlignItems.Center">
@@ -15,7 +16,7 @@
{
<MudAlert Severity="Severity.Warning">سفارش یافت نشد.</MudAlert>
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack"
Href="@RouteConstants.Store.Orders">
OnClick="GoBack">
بازگشت به سفارش‌ها
</MudButton>
}
@@ -1,6 +1,7 @@
using CMSMicroservice.Protobuf.Protos.UserOrder;
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Messages = CMSMicroservice.Protobuf.Protos;
namespace FrontOffice.Main.Pages.Store;
@@ -8,6 +9,7 @@ namespace FrontOffice.Main.Pages.Store;
public partial class OrderTracking : ComponentBase
{
[Inject] private OrderService OrderService { get; set; } = default!;
[Inject] private IJSRuntime JS { get; set; } = default!;
[Parameter] public long id { get; set; }
@@ -123,4 +125,6 @@ public partial class OrderTracking : ComponentBase
public bool IsCurrent { get; set; }
public string Date { get; set; } = "";
}
private async Task GoBack() => await JS.InvokeVoidAsync("history.back");
}
@@ -4,12 +4,10 @@
<PageTitle>سفارشات من</PageTitle>
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
<MudText Typo="Typo.h4" Class="mb-3">سفارشات من</MudText>
<PageHeader Title="سفارشات من" BackHref="@RouteConstants.Store.Products" />
@if (_loading)
{
<MudStack AlignItems="AlignItems.Center" Class="py-4">
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
</MudStack>
<LoadingState />
}
else if (_orders.Count == 0)
{
@@ -6,23 +6,22 @@
@if (_loading)
{
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
<MudStack AlignItems="AlignItems.Center">
<MudProgressCircular Color="Color.Primary" Indeterminate="true"/>
</MudStack>
<LoadingState />
</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>
<EmptyState Icon="@Icons.Material.Filled.SearchOff"
Title="محصول یافت نشد."
ActionText="بازگشت"
ActionHref="@RouteConstants.Store.Products" />
</MudContainer>
}
else
{
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
<PageHeader Title="جزئیات محصول" BackHref="@RouteConstants.Store.Products" />
<MudGrid Spacing="3">
<MudItem xs="12" md="6">
@if (_product is not null)
@@ -32,7 +31,9 @@ else
<MudPaper Class="rounded-lg pa-2">
@if (!string.IsNullOrWhiteSpace(MainImageUrl))
{
<div class="rounded-lg" style="width:100%;height:360px;background-image: url('@MainImageUrl');background-size: cover; background-position: center;border-radius: 0.5rem;color: transparent;" >@MainImageAlt</div>
<AppImage Path="@MainImageUrl" Alt="@MainImageAlt"
ObjectFit="ObjectFit.Cover"
Style="width:100%; max-height:400px; border-radius:12px;" />
}
else
{
@@ -45,7 +46,7 @@ else
@foreach (var item in _galleryItems)
{
<MudButton Variant="Variant.Text" Class="p-0" Style="min-width:0;" OnClick="() => SelectGalleryImage(item)">
<MudImage Src="@GetThumbnailUrl(item)" Alt="@item.Title" Class="rounded-sm" Style="@GetThumbnailStyle(item)" />
<AppImage Path="@GetThumbnailUrl(item)" Alt="@item.Title" Class="rounded-sm" Style="@GetThumbnailStyle(item)" />
</MudButton>
}
</MudStack>
@@ -3,7 +3,18 @@
<PageTitle>محصولات</PageTitle>
<MudContainer MaxWidth="MaxWidth.Large" Class="pa-2 pa-md-6">
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
<!-- Hero Banner -->
<MudPaper Class="store-hero pa-6 pa-md-8 mb-4 rounded-xl" Elevation="0">
<MudStack AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.Storefront" Size="Size.Large" Class="dash-hero-name" />
<MudText Typo="Typo.h5" Align="Align.Center" Class="dash-hero-name">فروشگاه کارا بازار سلامت</MudText>
<MudText Typo="Typo.body2" Align="Align.Center" Class="dash-hero-sub">
محصولات با کیفیت با ضمانت اصالت و ارسال سریع
</MudText>
</MudStack>
</MudPaper>
<MudPaper Elevation="1" Class="pa-3 pa-md-4 mb-4 rounded-lg">
<MudGrid Spacing="2" AlignItems="AlignItems.Center">
@* ردیف اول: جستجو و دکمه‌ها *@
@@ -44,25 +55,25 @@
Margin="Margin.Dense"
AnchorOrigin="Origin.BottomCenter"
AdornmentIcon="@Icons.Material.Filled.Sort">
<MudSelectItem Value="ProductSortOption.PriceDesc">
<MudSelectItem T="ProductSortOption" Value="ProductSortOption.PriceDesc">
<div class="d-flex align-center gap-2">
<MudIcon Icon="@Icons.Material.Filled.ArrowDownward" Size="Size.Small"/>
گران‌ترین
</div>
</MudSelectItem>
<MudSelectItem Value="ProductSortOption.PriceAsc">
<MudSelectItem T="ProductSortOption" Value="ProductSortOption.PriceAsc">
<div class="d-flex align-center gap-2">
<MudIcon Icon="@Icons.Material.Filled.ArrowUpward" Size="Size.Small"/>
ارزان‌ترین
</div>
</MudSelectItem>
<MudSelectItem Value="ProductSortOption.Newest">
<MudSelectItem T="ProductSortOption" Value="ProductSortOption.Newest">
<div class="d-flex align-center gap-2">
<MudIcon Icon="@Icons.Material.Filled.NewReleases" Size="Size.Small"/>
جدیدترین
</div>
</MudSelectItem>
<MudSelectItem Value="ProductSortOption.Title">
<MudSelectItem T="ProductSortOption" Value="ProductSortOption.Title">
<div class="d-flex align-center gap-2">
<MudIcon Icon="@Icons.Material.Filled.SortByAlpha" Size="Size.Small"/>
الفبایی