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,204 @@
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Service for fetching published blog posts (customer-facing).
|
||||
/// Used by Landing Page (latest posts) and future Blog pages.
|
||||
/// </summary>
|
||||
public class BlogPostService
|
||||
{
|
||||
private readonly CMSMicroservice.Protobuf.Protos.BlogPost.BlogPostContract.BlogPostContractClient _client;
|
||||
|
||||
public BlogPostService(CMSMicroservice.Protobuf.Protos.BlogPost.BlogPostContract.BlogPostContractClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get featured blog posts (for landing page hero / highlights).
|
||||
/// </summary>
|
||||
public async Task<List<BlogPostCardDto>> GetFeaturedPostsAsync(int count = 3)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _client.GetFeaturedBlogPostsAsync(
|
||||
new CMSMicroservice.Protobuf.Protos.BlogPost.GetFeaturedBlogPostsRequest { Count = count });
|
||||
|
||||
return MapToCardDtos(response?.Models);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine($"BlogPostService.GetFeaturedPostsAsync error: {ex.Message}");
|
||||
#endif
|
||||
return new();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get latest published posts with optional category filter.
|
||||
/// </summary>
|
||||
public async Task<BlogPostListResult> GetPublishedPostsAsync(int page = 1, int pageSize = 9, string? search = null, long? categoryId = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new CMSMicroservice.Protobuf.Protos.BlogPost.GetPublishedBlogPostsRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
|
||||
{
|
||||
PageNumber = page,
|
||||
PageSize = pageSize
|
||||
}
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
request.SearchTerm = search;
|
||||
if (categoryId.HasValue)
|
||||
request.CategoryId = categoryId.Value;
|
||||
|
||||
var response = await _client.GetPublishedBlogPostsAsync(request);
|
||||
|
||||
return new BlogPostListResult
|
||||
{
|
||||
Posts = MapToCardDtos(response?.Models),
|
||||
TotalCount = (int)(response?.MetaData?.TotalCount ?? 0),
|
||||
TotalPages = (int)(response?.MetaData?.TotalPage ?? 0),
|
||||
CurrentPage = page
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine($"BlogPostService.GetPublishedPostsAsync error: {ex.Message}");
|
||||
#endif
|
||||
return new();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a single blog post by slug (for blog detail page).
|
||||
/// </summary>
|
||||
public async Task<BlogPostDetailDto?> GetBySlugAsync(string slug)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _client.GetBlogPostBySlugAsync(
|
||||
new CMSMicroservice.Protobuf.Protos.BlogPost.GetBlogPostBySlugRequest { Slug = slug });
|
||||
|
||||
if (response == null || response.Id <= 0) return null;
|
||||
|
||||
return new BlogPostDetailDto
|
||||
{
|
||||
Id = response.Id,
|
||||
Title = response.Title,
|
||||
Slug = response.Slug,
|
||||
Summary = response.Summary,
|
||||
HtmlContent = response.HtmlContent,
|
||||
FeaturedImagePath = response.FeaturedImagePath,
|
||||
FeaturedImageThumbnailPath = response.FeaturedImageThumbnailPath,
|
||||
PublishedAt = response.PublishedAt?.ToDateTime(),
|
||||
ViewCount = response.ViewCount,
|
||||
IsFeatured = response.IsFeatured,
|
||||
Categories = response.Categories?.Select(c => new BlogCategoryInfo { Id = c.Id, Title = c.Title, Slug = c.Slug }).ToList() ?? new(),
|
||||
Tags = response.Tags?.Select(t => new BlogTagInfo { Id = t.Id, Title = t.Title, Name = t.Name }).ToList() ?? new()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine($"BlogPostService.GetBySlugAsync error: {ex.Message}");
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increment view count for a blog post.
|
||||
/// </summary>
|
||||
public async Task IncrementViewCountAsync(long postId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _client.IncrementViewCountAsync(
|
||||
new CMSMicroservice.Protobuf.Protos.BlogPost.IncrementViewCountRequest { Id = postId });
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fire and forget — don't fail the page
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
private static List<BlogPostCardDto> MapToCardDtos(
|
||||
Google.Protobuf.Collections.RepeatedField<CMSMicroservice.Protobuf.Protos.BlogPost.BlogPostListItem>? models)
|
||||
{
|
||||
if (models == null || models.Count == 0) return new();
|
||||
|
||||
return models.Select(m => new BlogPostCardDto
|
||||
{
|
||||
Id = m.Id,
|
||||
Title = m.Title,
|
||||
Slug = m.Slug,
|
||||
Summary = m.Summary,
|
||||
ThumbnailUrl = m.FeaturedImageThumbnailPath,
|
||||
PublishedAt = m.PublishedAt?.ToDateTime(),
|
||||
ViewCount = m.ViewCount,
|
||||
IsFeatured = m.IsFeatured,
|
||||
Categories = m.Categories?.Select(c => new BlogCategoryInfo { Id = c.Id, Title = c.Title, Slug = c.Slug }).ToList() ?? new()
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ── DTOs ──
|
||||
|
||||
public class BlogPostCardDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Slug { get; set; } = string.Empty;
|
||||
public string? Summary { get; set; }
|
||||
public string? ThumbnailUrl { get; set; }
|
||||
public DateTime? PublishedAt { get; set; }
|
||||
public int ViewCount { get; set; }
|
||||
public bool IsFeatured { get; set; }
|
||||
public List<BlogCategoryInfo> Categories { get; set; } = new();
|
||||
}
|
||||
|
||||
public class BlogPostDetailDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Slug { get; set; } = string.Empty;
|
||||
public string? Summary { get; set; }
|
||||
public string HtmlContent { get; set; } = string.Empty;
|
||||
public string? FeaturedImagePath { get; set; }
|
||||
public string? FeaturedImageThumbnailPath { get; set; }
|
||||
public DateTime? PublishedAt { get; set; }
|
||||
public int ViewCount { get; set; }
|
||||
public bool IsFeatured { get; set; }
|
||||
public List<BlogCategoryInfo> Categories { get; set; } = new();
|
||||
public List<BlogTagInfo> Tags { get; set; } = new();
|
||||
}
|
||||
|
||||
public class BlogPostListResult
|
||||
{
|
||||
public List<BlogPostCardDto> Posts { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
public int TotalPages { get; set; }
|
||||
public int CurrentPage { get; set; }
|
||||
}
|
||||
|
||||
public class BlogCategoryInfo
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Slug { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class BlogTagInfo
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
Reference in New Issue
Block a user