Implemented order history
continuous-integration/drone/pr Build is passing

This commit is contained in:
Khwezi Mngoma
2026-06-17 22:45:40 +02:00
parent 8d2efbeb4a
commit 1efe1ff21e
3 changed files with 347 additions and 184 deletions
+173 -71
View File
@@ -1,10 +1,26 @@
namespace MidrandBookshop.Components.Pages;
using LiteCharms.Features.Hasher;
using LiteCharms.Features.MidrandBooks.Customers;
using LiteCharms.Features.MidrandBooks.Customers.Models;
using LiteCharms.Features.MidrandBooks.Orders;
using LiteCharms.Features.MidrandBooks.Payments;
using LiteCharms.Features.MidrandBooks.Products;
namespace MidrandBookshop.Components.Pages;
public partial class Account : ComponentBase
{
[Inject] private AuthenticationStateProvider AuthStateProvider { get; set; } = default!;
[Inject] private CustomerService CustomerService { get; set; } = default!;
[Inject] private OrderService OrderService { get; set; } = default!;
[Inject] private PaymentService PaymentService { get; set; } = default!;
[Inject] private ProductService ProductService { get; set; } = default!;
[Inject] private HashService HashService { get; set; } = default!;
[Inject] private CancellationToken CancellationToken { get; set; } = default!;
[Inject] private IToastService ToasterService { get; set; } = default!;
private ClaimsPrincipal? User { get; set; }
private Customer? customer;
private bool showAddForm = false;
private AddressItem? editingAddress = null;
private string newAddressName = "";
@@ -13,17 +29,7 @@ public partial class Account : ComponentBase
private string newPostalCode = "";
private bool isBilling, isShipping;
private List<OrderItem> orderHistory = new()
{
// 1. Delivered + Paid (Green Mapping Rules)
new OrderItem { OrderId = "#MB-2026-8712", ProductId = "mastering-css-isolation", ProductTitle = "Mastering CSS Isolation in Modern .NET Web Applications Architecture", OrderDate = new DateTime(2026, 4, 14), ShippingAddressName = "Midrand Warehouse", Status = "Delivered", PaymentStatus = "Paid", Total = 890.00 },
// 2. Shipped + Paid (Amber Logistics + Green Payment Rules)
new OrderItem { OrderId = "#MB-2026-9481", ProductId = "introduction-to-blazor", ProductTitle = "Introduction to Blazor WebAssembly Framework Development", OrderDate = new DateTime(2026, 5, 20), ShippingAddressName = "Home Address", Status = "Shipped", PaymentStatus = "Paid", Total = 720.00 },
// 3. Unshipped + Abandoned/Unpaid (Muted Grey / Soft Red Rules — Hides Invoice Button)
new OrderItem { OrderId = "#MB-2026-1034", ProductId = "csharp-functional-paradigms", ProductTitle = "Advanced Functional Architecture & Monadic Paradigms in Modern C#", OrderDate = new DateTime(2026, 6, 11), ShippingAddressName = "Home Address", Status = "Unshipped", PaymentStatus = "Unpaid", Total = 650.00 }
};
private List<OrderItem> orderHistory = [];
private List<AddressItem> savedAddresses = new()
{
@@ -35,64 +41,138 @@ public partial class Account : ComponentBase
{
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
User = authState?.User;
var customerFetch = await CustomerService.GetCustomerAsync(User?.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)!.Value!, CancellationToken);
if (customerFetch.IsSuccess)
customer = customerFetch.Value;
await LoadOrdersAsync();
}
private async Task LoadOrdersAsync()
{
if (customer is null)
{
ToasterService.ShowError("There was a problem loading your details, please contact the administrator");
return;
}
var ordersFetch = await OrderService.GetOrdersByCustomerAsync(customer.Id, CancellationToken);
if (ordersFetch.IsFailed)
{
ToasterService.ShowWarning("No orders were found");
return;
}
orderHistory.Clear();
foreach (var order in ordersFetch.Value)
{
var paymentFetch = await PaymentService.GetOrderPaymentAsync(order.Id, CancellationToken);
var orderEntry = new OrderItem
{
OrderDate = order.CreatedAt,
OrderId = HashService.HashEncodeLongId(order.Id).Value,
Status = order.Status.ToString(),
PaymentStatus = paymentFetch.IsSuccess ? paymentFetch.Value.Status.ToString() : "NotPaid",
ShippingStatus = "Processing",
ShippingAddressName = "TBA",
Total = order.Total,
InvoiceUrl = order.InvoiceUrl!,
};
var orderItemsFetch = await OrderService.GetOrderItemsAsync(order.Id, CancellationToken);
if (orderItemsFetch.IsFailed) continue;
foreach (var item in orderItemsFetch.Value)
{
var productPriceFetch = await ProductService.GetProductPriceAsync(item.ProductPriceId, CancellationToken);
if (productPriceFetch.IsFailed) continue;
var productFetch = await ProductService.GetProductAsync(productPriceFetch.Value.ProductId, CancellationToken);
var itemEntry = new PurchasedBook
{
Quantity = item.Quantity,
PriceUnitPrice = productPriceFetch.Value.Amount,
CoverImageUrl = productFetch.Value.ImageUrl!,
Title = productFetch.Value.Name!,
};
orderEntry.PurchasedBooks.Add(itemEntry);
}
orderHistory.Add(orderEntry);
}
}
private void DownloadInvoice(string orderId)
{
Navigation.NavigateTo($"/api/invoices/download/{orderId.Replace("#", "")}", forceLoad: true);
var order = orderHistory.FirstOrDefault(o => o.OrderId == orderId)!;
if (string.IsNullOrWhiteSpace(order.InvoiceUrl))
ToasterService.ShowWarning("Your invoice is currently not availabe for viewing");
else
Navigation.NavigateTo(orderHistory.FirstOrDefault(o => o.OrderId == orderId)!.InvoiceUrl, forceLoad: true);
}
private string GetStatusClass(string status) => status?.ToLower() switch
// Badge Style 1: Core Order Lifecycle Status Mapping
private string GetOrderStatusClass(string? status)
{
"delivered" => "status-delivered", // Green
"shipped" => "status-shipped", // Amber
_ => "status-processing" // Muted Architectural Dark Grey
};
private string GetPaymentStatusClass(string paymentStatus) => paymentStatus?.ToLower() switch
{
"paid" => "pay-paid", // Green
"refunded" => "pay-refunded", // Grey
_ => "pay-pending" // Red Alert Tone
};
private void EditAddress(AddressItem addr)
{
editingAddress = addr;
showAddForm = false;
newAddressName = addr.Name;
newStreetAddress = addr.Street;
newCity = addr.City;
newPostalCode = addr.PostalCode;
isBilling = addr.IsBilling;
isShipping = addr.IsShipping;
return status?.ToLower() switch
{
"completed" => "order-completed",
"processing" => "order-processing",
"cancelled" => "order-cancelled",
_ => "order-hold"
};
}
private void CancelAddressActions()
// Badge Style 2: Financial Payment Status Mapping
private string GetPaymentStatusClass(string? status)
{
showAddForm = false;
editingAddress = null;
ClearFormFields();
return status?.ToLower() switch
{
"paid" => "pay-paid",
"refunded" => "pay-refunded",
_ => "pay-pending"
};
}
private void ClearFormFields()
// Badge Style 3: Logistics Shipment Status Mapping
private string GetShippingStatusClass(string? status)
{
newAddressName = "";
newStreetAddress = "";
newCity = "";
newPostalCode = "";
isBilling = false;
isShipping = false;
return status?.ToLower() switch
{
"delivered" => "status-delivered",
"shipped" => "status-shipped",
_ => "status-processing"
};
}
// Implemented to resolve UI registration click events
private void SaveAddress()
{
if (string.IsNullOrWhiteSpace(newAddressName) || string.IsNullOrWhiteSpace(newStreetAddress)) return;
if (string.IsNullOrWhiteSpace(newAddressName) || string.IsNullOrWhiteSpace(newStreetAddress))
{
ToasterService.ShowWarning("Please fill in the required fields.");
return;
}
if (editingAddress == null)
if (editingAddress != null)
{
editingAddress.Name = newAddressName;
editingAddress.Street = newStreetAddress;
editingAddress.City = newCity;
editingAddress.PostalCode = newPostalCode;
editingAddress.IsBilling = isBilling;
editingAddress.IsShipping = isShipping;
}
else
{
var nextId = savedAddresses.Any() ? savedAddresses.Max(a => a.Id) + 1 : 1;
var newAddr = new AddressItem
savedAddresses.Add(new AddressItem
{
Id = nextId,
Name = newAddressName,
@@ -102,26 +182,39 @@ public partial class Account : ComponentBase
IsBilling = isBilling,
IsShipping = isShipping,
IsPrimary = !savedAddresses.Any()
};
savedAddresses.Add(newAddr);
}
else
{
var target = savedAddresses.FirstOrDefault(a => a.Id == editingAddress.Id);
if (target != null)
{
target.Name = newAddressName;
target.Street = newStreetAddress;
target.City = newCity;
target.PostalCode = newPostalCode;
target.IsBilling = isBilling;
target.IsShipping = isShipping;
}
editingAddress = null;
});
}
CancelAddressActions();
}
private void CancelAddressActions()
{
showAddForm = false;
editingAddress = null;
ResetFormFields();
}
private void ResetFormFields()
{
newAddressName = "";
newStreetAddress = "";
newCity = "";
newPostalCode = "";
isBilling = false;
isShipping = false;
}
private void EditAddress(AddressItem addr)
{
editingAddress = addr;
newAddressName = addr.Name;
newStreetAddress = addr.Street;
newCity = addr.City;
newPostalCode = addr.PostalCode;
isBilling = addr.IsBilling;
isShipping = addr.IsShipping;
showAddForm = false;
ClearFormFields();
}
private void DeleteAddress(AddressItem addr)
@@ -161,12 +254,21 @@ public partial class Account : ComponentBase
public class OrderItem
{
public string OrderId { get; set; } = "";
public string ProductId { get; set; } = "";
public string ProductTitle { get; set; } = "";
public DateTime OrderDate { get; set; }
public string ShippingAddressName { get; set; } = "";
public string Status { get; set; } = "";
public string PaymentStatus { get; set; } = "Pending";
public double Total { get; set; }
public string PaymentStatus { get; set; } = "";
public string ShippingStatus { get; set; } = "";
public string InvoiceUrl { get; set; } = "";
public decimal Total { get; set; }
public List<PurchasedBook> PurchasedBooks { get; set; } = new();
}
public class PurchasedBook
{
public string Title { get; set; } = "";
public string CoverImageUrl { get; set; } = "";
public int Quantity { get; set; }
public decimal PriceUnitPrice { get; set; }
}
}