feat: full FrontOffice updates - discount store, blog, payment gateway, UI improvements
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 4m55s
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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user