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
@@ -0,0 +1,140 @@
@attribute [Route("/discount-store/product/{Id:long}")]
<PageTitle>@(_product?.Title ?? "محصول تخفیفی")</PageTitle>
<MudContainer MaxWidth="MaxWidth.Large" Class="py-4 py-md-6">
@if (_loading)
{
<LoadingState />
}
else if (_product is null)
{
<EmptyState Icon="@Icons.Material.Filled.SearchOff"
Title="محصول مورد نظر یافت نشد."
ActionText="بازگشت به فروشگاه تخفیفی"
ActionHref="@RouteConstants.DiscountStore.Products" />
}
else
{
<!-- Breadcrumb -->
<PageHeader Title="جزئیات محصول تخفیفی" BackHref="@RouteConstants.DiscountStore.Products" />
<MudGrid Spacing="4">
<!-- Image Gallery -->
<MudItem xs="12" md="5">
<MudPaper Class="pa-2 rounded-xl" Elevation="1">
<AppImage Path="@_selectedImage" Alt="@_product.Title"
ObjectFit="ObjectFit.Contain"
Style="width:100%; max-height:400px; border-radius:12px;" />
@if (_product.Images.Count > 1)
{
<MudStack Row="true" Spacing="1" Class="mt-2 flex-wrap" Justify="Justify.Center">
@foreach (var img in _product.Images)
{
<span @onclick="() => _selectedImage = img.ImageUrl" style="cursor:pointer">
<AppImage Path="@img.ThumbnailUrl" Alt="@(img.Title ?? "")"
ImgWidth="60" ImgHeight="60"
ObjectFit="ObjectFit.Cover"
Class="rounded-lg"
Style="@($"border: 2px solid {(img.ImageUrl == _selectedImage ? "var(--mud-palette-primary)" : "transparent")};")"
/>
</span>
}
</MudStack>
}
</MudPaper>
</MudItem>
<!-- Product Info -->
<MudItem xs="12" md="7">
<MudStack Spacing="3">
<MudText Typo="Typo.h5" Class="fw-bold">@_product.Title</MudText>
<!-- Categories -->
@if (_product.Categories.Any())
{
<MudStack Row="true" Spacing="1" Class="flex-wrap">
@foreach (var cat in _product.Categories)
{
<MudChip T="string" Color="Color.Info" Variant="Variant.Outlined" Size="Size.Small">@cat.Title</MudChip>
}
</MudStack>
}
<!-- Price & Discount -->
<MudPaper Class="pa-4 rounded-lg discount-price-box" Elevation="0">
<MudStack Spacing="2">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body2" Class="mud-text-secondary">قیمت محصول:</MudText>
<MudText Typo="Typo.h5" Color="Color.Primary" Class="fw-bold">
@($"{_product.Price:N0}") تومان
</MudText>
</MudStack>
@if (_product.MaxDiscountPercent > 0)
{
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body2" Class="mud-text-secondary">سقف پرداخت از کیف تخفیفی:</MudText>
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small">
تا @_product.MaxDiscountPercent% (تا @($"{_product.Price * _product.MaxDiscountPercent / 100:N0}") تومان)
</MudChip>
</MudStack>
}
</MudStack>
</MudPaper>
<!-- Stock & Stats -->
<MudStack Row="true" Spacing="3" Class="flex-wrap">
<MudChip T="string" Variant="Variant.Outlined" Size="Size.Small"
Color="@(_product.RemainingCount > 0 ? Color.Success : Color.Error)"
Icon="@Icons.Material.Filled.Inventory">
@(_product.RemainingCount > 0 ? $"موجود ({_product.RemainingCount} عدد)" : "ناموجود")
</MudChip>
<MudChip T="string" Variant="Variant.Outlined" Size="Size.Small" Color="Color.Default"
Icon="@Icons.Material.Filled.Visibility">
@_product.ViewCount بازدید
</MudChip>
</MudStack>
<!-- Short Info -->
@if (!string.IsNullOrWhiteSpace(_product.ShortInfo))
{
<MudText Typo="Typo.body1" Class="mud-text-secondary">@_product.ShortInfo</MudText>
}
<!-- Add to Cart -->
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center" Class="flex-wrap">
<MudNumericField T="int" @bind-Value="_quantity" Min="1"
Max="@(_product.RemainingCount > 0 ? _product.RemainingCount : 1)"
Label="تعداد" Variant="Variant.Outlined" Margin="Margin.Dense"
Style="width:100px;" />
<MudButton Variant="Variant.Filled" Color="Color.Success"
StartIcon="@Icons.Material.Filled.AddShoppingCart"
OnClick="AddToCart"
Disabled="@(_product.RemainingCount <= 0 || _addingToCart)"
Size="Size.Large">
@(_addingToCart ? "در حال افزودن..." : "افزودن به سبد")
</MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.ShoppingCart"
Href="@RouteConstants.DiscountStore.Cart">
مشاهده سبد
</MudButton>
</MudStack>
</MudStack>
</MudItem>
</MudGrid>
<!-- Full Information -->
@if (!string.IsNullOrWhiteSpace(_product.FullInfo))
{
<MudPaper Elevation="1" Class="pa-4 pa-md-6 mt-6 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">توضیحات محصول</MudText>
<MudDivider Class="mb-4" />
<div class="blog-content">
@((MarkupString)_product.FullInfo)
</div>
</MudPaper>
}
}
</MudContainer>