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,207 @@
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
using CMSMicroservice.Protobuf.Protos.DiscountCategory;
using Google.Protobuf.WellKnownTypes;
namespace FrontOffice.Main.Utilities;
// ── DTOs ──
public record DiscountProductCard(
long Id,
string Title,
string ShortInfo,
long Price,
int MaxDiscountPercent,
string ImageUrl,
string ThumbnailUrl,
int RemainingCount,
int ViewCount,
bool IsActive,
DateTime Created);
public record DiscountProductDetail(
long Id,
string Title,
string ShortInfo,
string FullInfo,
long Price,
int MaxDiscountPercent,
string ImageUrl,
string ThumbnailUrl,
int RemainingCount,
int ViewCount,
int SortOrder,
bool IsActive,
List<DiscountCategoryInfo> Categories,
List<DiscountProductImageDto> Images,
DateTime Created);
public record DiscountCategoryInfo(long Id, string Name, string Title);
public record DiscountProductImageDto(
long Id,
string ImageUrl,
string ThumbnailUrl,
string? Title,
string? AltText,
int SortOrder);
public record DiscountCategoryNode(
long Id,
string Name,
string Title,
string? Description,
string? ImagePath,
long? ParentCategoryId,
int SortOrder,
bool IsActive,
int ProductCount,
List<DiscountCategoryNode> Children);
public record DiscountProductListResult(
List<DiscountProductCard> Products,
int TotalCount,
int TotalPages,
int CurrentPage);
// ── Service ──
public class DiscountProductService
{
private readonly DiscountProductContract.DiscountProductContractClient _productClient;
private readonly DiscountCategoryContract.DiscountCategoryContractClient _categoryClient;
public DiscountProductService(
DiscountProductContract.DiscountProductContractClient productClient,
DiscountCategoryContract.DiscountCategoryContractClient categoryClient)
{
_productClient = productClient;
_categoryClient = categoryClient;
}
public async Task<DiscountProductListResult> GetProductsAsync(
int page = 1, int pageSize = 12,
string? search = null, long? categoryId = null,
bool? inStock = null)
{
try
{
var request = new GetDiscountProductsRequest
{
PageNumber = page,
PageSize = pageSize,
IsActive = true // فقط محصولات فعال
};
if (!string.IsNullOrWhiteSpace(search))
request.SearchQuery = search;
if (categoryId.HasValue)
request.CategoryId = categoryId.Value;
if (inStock.HasValue)
request.InStock = inStock.Value;
var response = await _productClient.GetDiscountProductsAsync(request);
var products = response.Models.Select(m => new DiscountProductCard(
Id: m.Id,
Title: m.Title ?? string.Empty,
ShortInfo: m.ShortInfomation ?? string.Empty,
Price: m.Price,
MaxDiscountPercent: m.MaxDiscountPercent,
ImageUrl: BuildUrl(m.ImagePath),
ThumbnailUrl: BuildUrl(m.ThumbnailPath),
RemainingCount: m.RemainingCount,
ViewCount: m.ViewCount,
IsActive: m.IsActive,
Created: m.Created?.ToDateTime() ?? DateTime.MinValue
)).ToList();
var totalCount = (int)(response.MetaData?.TotalCount ?? 0);
var totalPages = (int)(response.MetaData?.TotalPage ?? 0);
return new DiscountProductListResult(products, totalCount, totalPages, page);
}
catch
{
return new DiscountProductListResult(new(), 0, 0, page);
}
}
public async Task<DiscountProductDetail?> GetByIdAsync(long productId)
{
try
{
var response = await _productClient.GetDiscountProductByIdAsync(
new GetDiscountProductByIdRequest { ProductId = productId });
var categories = response.Categories
.Select(c => new DiscountCategoryInfo(c.Id, c.Name ?? "", c.Title ?? ""))
.ToList();
// Load images
var imagesResponse = await _productClient.GetDiscountProductImagesAsync(
new GetDiscountProductImagesRequest { DiscountProductId = productId, OnlyActive = true });
var images = imagesResponse.Images
.OrderBy(i => i.SortOrder)
.Select(i => new DiscountProductImageDto(
Id: i.Id,
ImageUrl: BuildUrl(i.ImagePath),
ThumbnailUrl: BuildUrl(i.ThumbnailPath),
Title: i.Title,
AltText: i.AltText,
SortOrder: i.SortOrder))
.ToList();
return new DiscountProductDetail(
Id: response.Id,
Title: response.Title ?? string.Empty,
ShortInfo: response.ShortInfomation ?? string.Empty,
FullInfo: response.FullInformation ?? string.Empty,
Price: response.Price,
MaxDiscountPercent: response.MaxDiscountPercent,
ImageUrl: BuildUrl(response.ImagePath),
ThumbnailUrl: BuildUrl(response.ThumbnailPath),
RemainingCount: response.RemainingCount,
ViewCount: response.ViewCount,
SortOrder: response.SortOrder,
IsActive: response.IsActive,
Categories: categories,
Images: images,
Created: response.Created?.ToDateTime() ?? DateTime.MinValue);
}
catch
{
return null;
}
}
public async Task<List<DiscountCategoryNode>> GetCategoriesAsync(bool onlyActive = true)
{
try
{
var request = new GetDiscountCategoriesRequest();
if (onlyActive) request.IsActive = true;
var response = await _categoryClient.GetDiscountCategoriesAsync(request);
return response.Categories.Select(MapCategory).ToList();
}
catch
{
return new();
}
}
private static DiscountCategoryNode MapCategory(DiscountCategoryDto dto) => new(
Id: dto.Id,
Name: dto.Name ?? "",
Title: dto.Title ?? "",
Description: dto.Description,
ImagePath: dto.ImagePath,
ParentCategoryId: dto.ParentCategoryId,
SortOrder: dto.SortOrder,
IsActive: dto.IsActive,
ProductCount: dto.ProductCount,
Children: dto.Children.Select(MapCategory).ToList());
private static string BuildUrl(string? path)
=> path ?? string.Empty;
}