This commit is contained in:
2025-10-27 17:39:18 -04:00
commit 31f723bea4
1579 changed files with 642409 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
using System.Net;
using System.Net.Http.Json;
using Prefab.Web.Client.Models.Home;
using Prefab.Web.Client.Models.Shared;
namespace Prefab.Web.Client.Services;
public interface IHomePageService
{
Task<Result<HomePageModel>> Get(CancellationToken cancellationToken);
}
public sealed class HomePageService(HttpClient httpClient) : IHomePageService
{
public async Task<Result<HomePageModel>> Get(CancellationToken cancellationToken)
{
using var response = await httpClient.GetAsync("bff/home", cancellationToken);
var result = await response.Content.ReadFromJsonAsync<Result<HomePageModel>>(cancellationToken: cancellationToken);
if (result is not null)
{
return result;
}
var status = (HttpStatusCode)response.StatusCode;
var exception = new InvalidOperationException($"Response {(int)status} did not contain a valid payload.");
var problem = ResultProblem.Unexpected("Failed to retrieve home page content.", exception, status);
return Result<HomePageModel>.Failure(problem);
}
}

View File

@@ -0,0 +1,30 @@
using System.Net.Http.Json;
using Prefab.Web.Client.Models.Shared;
using Prefab.Web.Client.Pages;
namespace Prefab.Web.Client.Services;
public interface ICategoriesPageService
{
Task<Result<CategoriesPageModel>> GetPage(CancellationToken cancellationToken);
}
public sealed class CategoriesPageService(HttpClient httpClient) : ICategoriesPageService
{
public async Task<Result<CategoriesPageModel>> GetPage(CancellationToken cancellationToken)
{
using var response = await httpClient.GetAsync("bff/categories/page", cancellationToken);
var result = await response.Content.ReadFromJsonAsync<Result<CategoriesPageModel>>(cancellationToken);
if (result is not null)
{
return result;
}
var exception = new InvalidOperationException($"Response {(int)response.StatusCode} did not contain a valid payload.");
var unexpectedProblem = ResultProblem.Unexpected("Failed to retrieve categories.", exception, response.StatusCode);
return Result<CategoriesPageModel>.Failure(unexpectedProblem);
}
}

View File

@@ -0,0 +1,60 @@
using System.Net;
using System.Net.Http.Json;
using Prefab.Web.Client.Models.Shared;
namespace Prefab.Web.Client.Services;
public interface INavMenuService
{
Task<Result<NavMenuModel>> Get(int depth, CancellationToken cancellationToken);
}
public sealed class NavMenuService : INavMenuService
{
private readonly HttpClient _httpClient;
public NavMenuService(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
public async Task<Result<NavMenuModel>> Get(int depth, CancellationToken cancellationToken)
{
var normalizedDepth = Math.Clamp(depth, 1, 2);
try
{
using var response = await _httpClient.GetAsync($"/bff/nav-menu/{normalizedDepth}", cancellationToken);
var payload = await response.Content.ReadFromJsonAsync<Result<NavMenuModel>>(cancellationToken: cancellationToken);
if (payload is not null)
{
return payload;
}
if (!response.IsSuccessStatusCode)
{
var statusCode = (HttpStatusCode)response.StatusCode;
var problem = ResultProblem.Unexpected(
"Failed to load navigation menu.",
new HttpRequestException($"Navigation menu request failed with status code {(int)statusCode} ({statusCode})."),
statusCode);
return Result<NavMenuModel>.Failure(problem);
}
var emptyProblem = ResultProblem.Unexpected(
"Failed to load navigation menu.",
new InvalidOperationException("Navigation menu response body was empty."),
(HttpStatusCode)response.StatusCode);
return Result<NavMenuModel>.Failure(emptyProblem);
}
catch (Exception exception)
{
var problem = ResultProblem.Unexpected("Failed to load navigation menu.", exception);
return Result<NavMenuModel>.Failure(problem);
}
}
}

View File

@@ -0,0 +1,78 @@
using Telerik.Blazor;
using Telerik.Blazor.Components;
namespace Prefab.Web.Client.Services;
/// <summary>
/// Defines a contract for displaying notifications of various types, such as errors, successes, and warnings, within an
/// application.
/// </summary>
/// <remarks>Implementations of this interface provide methods to present user-facing messages in a consistent
/// manner. The interface supports attaching a notification component and displaying messages with different severity
/// levels. This is typically used to inform users about the outcome of operations or to alert them to important
/// events.</remarks>
public interface INotificationService
{
void Attach(TelerikNotification notification);
void ShowError(string message);
void ShowSuccess(string message);
void ShowWarning(string message);
}
/// <summary>
/// Provides functionality to display error, success, and warning notifications using a Telerik notification component.
/// </summary>
/// <remarks>This service queues notifications if the Telerik notification component is not yet attached, and
/// displays them once the component becomes available. It is intended to be used as an abstraction for showing
/// user-facing notifications in applications that utilize Telerik UI components.</remarks>
public class NotificationService : INotificationService
{
private readonly Queue<NotificationModel> _pending = new();
private TelerikNotification? _notification;
public void Attach(TelerikNotification notification)
{
_notification = notification ?? throw new ArgumentNullException(nameof(notification));
FlushPending();
}
public void ShowError(string message) =>
Show(message, ThemeConstants.Notification.ThemeColor.Error);
public void ShowSuccess(string message) =>
Show(message, ThemeConstants.Notification.ThemeColor.Success);
public void ShowWarning(string message) =>
Show(message, ThemeConstants.Notification.ThemeColor.Warning);
private void Show(string message, string themeColor)
{
var model = new NotificationModel
{
Text = message,
ThemeColor = themeColor,
CloseAfter = 3000
};
if (_notification is { } notification)
{
notification.Show(model);
return;
}
_pending.Enqueue(model);
}
private void FlushPending()
{
if (_notification is not { } notification)
{
return;
}
while (_pending.Count > 0)
{
notification.Show(_pending.Dequeue());
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Net;
using System.Net.Http.Json;
using Prefab.Web.Client.Models.Catalog;
using Prefab.Web.Client.Models.Shared;
namespace Prefab.Web.Client.Services;
public interface IProductListingService
{
Task<Result<ProductListingModel>> GetCategoryProducts(string categorySlug, CancellationToken cancellationToken);
}
public sealed class ProductListingService(HttpClient httpClient) : IProductListingService
{
public async Task<Result<ProductListingModel>> GetCategoryProducts(string categorySlug, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(categorySlug);
using var response = await httpClient.GetAsync(
$"/bff/catalog/categories/{Uri.EscapeDataString(categorySlug)}" +
"/models",
cancellationToken);
var payload = await response.Content.ReadFromJsonAsync<Result<ProductListingModel>>(cancellationToken: cancellationToken);
if (payload is not null)
{
return payload;
}
var statusCode = (HttpStatusCode)response.StatusCode;
var exception = new InvalidOperationException($"Response {(int)statusCode} did not contain a valid payload.");
var unexpectedProblem = ResultProblem.Unexpected("Failed to retrieve category products.", exception, statusCode);
return Result<ProductListingModel>.Failure(unexpectedProblem);
}
}