Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 172575bb61 | |||
| 1efe1ff21e | |||
| 925456f35b | |||
| 7e853430ee | |||
| 16f7e1981e | |||
| 867fad8584 | |||
| 55d241e362 | |||
| 0da92cfb5a | |||
| 30cde40d5b | |||
| 5abe7a1476 | |||
| ace7eeef8e | |||
| e21fd59b12 | |||
| 956c19b9f7 | |||
| f27433a277 | |||
| 7294e5470f | |||
| 5db926f4c6 | |||
| a4460888af | |||
| 9de7abc3fb | |||
| e9b2e958d2 | |||
| 44df489406 | |||
| 0ea31a33ae | |||
| 4f44d0c597 | |||
| fbde2ea1a9 | |||
| 651682156c | |||
| e81789f8c6 | |||
| b9f3274633 | |||
| 552e9ff1b4 | |||
| 629dbe7cfe | |||
| 25acd67485 | |||
| d3672a6db9 | |||
| a8056e7a9a | |||
| 4458a1e189 | |||
| 2aeeb7a240 | |||
| 378044d011 | |||
| 4e42d9f21a | |||
| 0b7476d31c | |||
| 925c1f5988 | |||
| 9629d9ddf9 | |||
| 7a11572294 | |||
| a75bf5951d | |||
| bbcf64aa65 | |||
| a688bc816a | |||
| 4fe801583e | |||
| af3d40531b | |||
| bc2b9f81e0 | |||
| 49279c0cec | |||
| edabe266e5 | |||
| 248dd32b1b | |||
| 1645b6bbae | |||
| 72725a302a | |||
| f3d79174be | |||
| c086aa60e4 | |||
| 66b377bf69 | |||
| 82389a9304 | |||
| 4bf1d2e77a | |||
| 2b6576de85 | |||
| 7de957ed6f | |||
| 16fdcc8005 | |||
| a614d14da5 | |||
| b722ea2cd0 | |||
| 73145fd360 | |||
| 7f29680993 | |||
| 56626d2693 | |||
| f9f6788c79 | |||
| 249ad319d9 | |||
| 4a476febf4 |
@@ -17,6 +17,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path><line x1="3" y1="6" x2="21" y2="6"></line><path d="M16 10a4 4 0 0 1-8 0"></path></svg>
|
||||
<span>Order History</span>
|
||||
</button>
|
||||
|
||||
<button class="nav-link text-start d-flex align-items-center gap-2" data-bs-toggle="pill" data-bs-target="#shipping" role="tab">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
|
||||
<span>Shipping Address</span>
|
||||
@@ -39,7 +40,7 @@
|
||||
<div class="tab-content account-panels-deck">
|
||||
|
||||
<div class="tab-pane fade show active" id="orders" role="tabpanel">
|
||||
<div class="panel-card-wrapper mb-4">
|
||||
<div class="tab-panel-body">
|
||||
<h5 class="panel-section-title fw-bold text-dark font-monospace text-uppercase tracking-wider mb-4">Order History</h5>
|
||||
|
||||
@if (orderHistory == null || !orderHistory.Any())
|
||||
@@ -54,7 +55,7 @@
|
||||
@foreach (var order in orderHistory)
|
||||
{
|
||||
<div class="premium-order-card p-4 border rounded-3 bg-white shadow-sm">
|
||||
<div class="d-flex justify-content-between align-items-start flex-wrap gap-3 pb-3 border-b-dashed mb-3">
|
||||
<div class="d-flex justify-content-between align-items-start flex-wrap gap-3 pb-3 border-bottom border-light mb-3">
|
||||
<div>
|
||||
<span class="font-monospace text-dark fw-bold d-block h6 mb-1">@order.OrderId</span>
|
||||
<small class="text-muted d-block mb-1">Ordered on @order.OrderDate.ToString("dd MMMM yyyy")</small>
|
||||
@@ -63,20 +64,26 @@
|
||||
<span>Shipped to: <span class="fw-semibold text-dark">@order.ShippingAddressName</span></span>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="text-md-end d-flex flex-column align-items-md-end gap-2">
|
||||
<div class="d-flex align-items-center gap-1.5 flex-wrap justify-content-md-end">
|
||||
<span class="badge status-badge-base @GetOrderStatusClass(order.Status)">
|
||||
Order: @order.Status
|
||||
</span>
|
||||
|
||||
<span class="badge status-badge-base @GetPaymentStatusClass(order.PaymentStatus)">
|
||||
Pay: @order.PaymentStatus
|
||||
</span>
|
||||
<span class="badge status-badge-base @GetStatusClass(order.Status)">
|
||||
Logistics: @order.Status
|
||||
|
||||
<span class="badge status-badge-base @GetShippingStatusClass(order.ShippingStatus)">
|
||||
Logistics: @order.ShippingStatus
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if (order.PaymentStatus?.ToLower() == "paid")
|
||||
{
|
||||
<button class="btn btn-outline-dark btn-premium-sm font-monospace text-uppercase d-inline-flex align-items-center gap-1.5 py-1 px-2.5"
|
||||
style="font-size: 0.7rem;"
|
||||
style="font-size: 0.7rem;" disabled="@string.IsNullOrWhiteSpace(order.InvoiceUrl)"
|
||||
@onclick="() => DownloadInvoice(order.OrderId)">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
||||
<span>Invoice</span>
|
||||
@@ -85,12 +92,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="order-manifest-details d-flex align-items-center justify-content-between gap-4 py-1">
|
||||
<div class="item-meta">
|
||||
<h6 class="text-dark fw-bold mb-0 small" style="line-height: 1.4;">@order.ProductTitle</h6>
|
||||
</div>
|
||||
<div class="item-value text-end flex-shrink-0">
|
||||
<span class="font-monospace text-dark fw-bold d-block">R @order.Total.ToString("F2")</span>
|
||||
<div class="order-books-manifest manifest-grid-wrap mb-3">
|
||||
@foreach (var book in order.PurchasedBooks)
|
||||
{
|
||||
<div class="manifest-grid-cell">
|
||||
<div class="manifest-book-item border rounded p-2 d-flex align-items-center justify-content-between bg-light bg-opacity-20 h-100">
|
||||
<div class="d-flex align-items-center gap-2.5 min-w-0">
|
||||
<div class="book-thumbnail-container flex-shrink-0 border rounded bg-white shadow-xs">
|
||||
<img src="@book.CoverImageUrl" alt="@book.Title" class="book-thumbnail-img" />
|
||||
</div>
|
||||
<div class="book-text-meta min-w-0">
|
||||
<h6 class="text-dark fw-bold mb-0.5 small text-truncate" title="@book.Title">@book.Title</h6>
|
||||
<span class="badge bg-white text-secondary border font-monospace extra-small py-0.5">Qty: @book.Quantity</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="book-row-pricing text-end font-monospace text-dark small ps-2 flex-shrink-0 fw-medium">
|
||||
R @((book.PriceUnitPrice * book.Quantity).ToString("F2"))
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="order-summary-footer border-top pt-3 d-flex align-items-baseline justify-content-between">
|
||||
<span class="text-muted small text-uppercase font-monospace">Order Total</span>
|
||||
<div class="text-end">
|
||||
<span class="font-monospace text-dark fw-bold h5 mb-0 d-block">R @order.Total.ToString("F2")</span>
|
||||
<small class="text-muted extra-small font-monospace">VAT Inclusive</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -102,7 +129,7 @@
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="shipping" role="tabpanel">
|
||||
<div class="panel-card-wrapper mb-4">
|
||||
<div class="tab-panel-body">
|
||||
<div class="d-flex justify-content-between align-items-baseline mb-4">
|
||||
<h5 class="panel-section-title fw-bold text-dark font-monospace text-uppercase tracking-wider mb-0">Saved Addresses</h5>
|
||||
@if (!showAddForm && editingAddress == null)
|
||||
@@ -194,7 +221,7 @@
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="profile" role="tabpanel">
|
||||
<div class="panel-card-wrapper mb-4">
|
||||
<div class="tab-panel-body">
|
||||
<h5 class="panel-section-title fw-bold text-dark font-monospace text-uppercase tracking-wider mb-4">Profile Settings</h5>
|
||||
|
||||
<div class="profile-hero-banner mb-4 d-flex align-items-center justify-content-between p-4 border rounded-3 bg-light bg-opacity-20 flex-wrap gap-3">
|
||||
@@ -203,9 +230,7 @@
|
||||
<h5 class="fw-bold text-dark mb-1 h6">@User?.Identity?.Name</h5>
|
||||
<p class="text-muted small mb-0 font-monospace extra-small opacity-75">Secure Connection Authorized</p>
|
||||
</div>
|
||||
<span class="badge rounded-pill bg-success bg-opacity-10 text-success border border-success border-opacity-20 font-monospace px-3 py-1.5 small text-uppercase tracking-wide">
|
||||
Verified
|
||||
</span>
|
||||
<span class="badge rounded-pill bg-success bg-opacity-10 text-success border border-success border-opacity-20 font-monospace px-3 py-1.5 small text-uppercase tracking-wide">Verified</span>
|
||||
</div>
|
||||
|
||||
<div class="card p-5 text-center bg-white border rounded-3 shadow-sm my-4">
|
||||
@@ -219,7 +244,6 @@
|
||||
<p class="text-muted small mx-auto mb-4" style="max-width: 480px; line-height: 1.5;">
|
||||
For your structural protection, password alterations, account recovery preferences, cross-tenant factors, and core credential manifests are handled through our global Identity Node security layer.
|
||||
</p>
|
||||
|
||||
<a href="https://sts.security.khongisa.co.za/Manage/Index?returnUrl=https://midrandbooks.co.za/account"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,25 @@
|
||||
========================================================================== */
|
||||
|
||||
.account-page-container {
|
||||
max-width: 1140px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
.tab-panel-body {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.tab-panel-body {
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.account-main-title {
|
||||
font-size: 2.25rem;
|
||||
letter-spacing: -0.03em;
|
||||
@@ -39,102 +51,99 @@
|
||||
.account-nav-stack .nav-link.active {
|
||||
background-color: #111111 !important;
|
||||
color: #FFFFFF !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.account-nav-stack .nav-link:hover:not(.active):not(.nav-logout) {
|
||||
color: #111111;
|
||||
.account-nav-stack .nav-link:hover:not(.active) {
|
||||
background-color: rgba(0, 0, 0, 0.04) !important;
|
||||
transform: translateX(2px);
|
||||
color: #111111;
|
||||
}
|
||||
|
||||
.account-nav-stack .nav-logout:hover {
|
||||
background-color: #FFF5F5 !important;
|
||||
color: #DC3545 !important;
|
||||
.nav-logout:hover {
|
||||
background-color: rgba(220, 53, 69, 0.08) !important;
|
||||
}
|
||||
|
||||
/* --- Main Tabbed Layout Container Content Structures --- */
|
||||
.panel-card-wrapper {
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 12px;
|
||||
padding: 2.2rem;
|
||||
}
|
||||
|
||||
.panel-section-title {
|
||||
font-size: 0.9rem;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/* --- Order History Structured Panel Cards --- */
|
||||
.premium-order-card {
|
||||
transition: transform 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.premium-order-card:hover {
|
||||
border-color: rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
.border-b-dashed {
|
||||
border-bottom: 1px dashed rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* Minimalist Curated Logistics and Payment Badges Matrix */
|
||||
/* --- Balanced Status Mapping Badges --- */
|
||||
.status-badge-base {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 0.65rem !important;
|
||||
letter-spacing: 0.05em;
|
||||
padding: 0.3rem 0.6rem !important;
|
||||
border-radius: 4px !important;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
padding: 0.45rem 0.65rem;
|
||||
letter-spacing: 0.02em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Fulfillment Matrix Colors */
|
||||
/* 1. Core Order Lifecycle Badge Styles */
|
||||
.order-completed {
|
||||
background-color: #111111 !important;
|
||||
color: #FFFFFF !important;
|
||||
border: 1px solid #111111;
|
||||
}
|
||||
|
||||
.order-processing {
|
||||
background-color: rgba(0, 0, 0, 0.03) !important;
|
||||
color: #111111 !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.order-hold {
|
||||
background-color: rgba(108, 117, 125, 0.05) !important;
|
||||
color: #495057 !important;
|
||||
border: 1px solid rgba(108, 117, 125, 0.2);
|
||||
}
|
||||
|
||||
.order-cancelled {
|
||||
background-color: rgba(220, 53, 69, 0.04) !important;
|
||||
color: #842029 !important;
|
||||
border: 1px solid rgba(220, 53, 69, 0.12);
|
||||
}
|
||||
|
||||
/* 2. Logistics Shipment Status Badge Styles */
|
||||
.status-delivered {
|
||||
background-color: #E2F0D9 !important; /* Soft Green Match */
|
||||
color: #385723 !important;
|
||||
border: 1px solid rgba(56, 87, 35, 0.15);
|
||||
background-color: rgba(25, 135, 84, 0.06) !important;
|
||||
color: #198754 !important;
|
||||
border: 1px solid rgba(25, 135, 84, 0.15);
|
||||
}
|
||||
|
||||
.status-shipped {
|
||||
background-color: #FFF3CD !important; /* Warm Gold Amber */
|
||||
color: #856404 !important;
|
||||
border: 1px solid rgba(133, 100, 4, 0.12);
|
||||
background-color: rgba(13, 110, 253, 0.06) !important;
|
||||
color: #0d6efd !important;
|
||||
border: 1px solid rgba(13, 110, 253, 0.15);
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background-color: #F2F2F2 !important; /* Architectural Neutral Muted Grey */
|
||||
color: #595959 !important;
|
||||
border: 1px dashed rgba(0, 0, 0, 0.12);
|
||||
background-color: rgba(255, 193, 7, 0.08) !important;
|
||||
color: #b58100 !important;
|
||||
border: 1px solid rgba(255, 193, 7, 0.25);
|
||||
}
|
||||
|
||||
/* Financial Matrix Colors */
|
||||
/* 3. Financial Payment Status Badge Styles */
|
||||
.pay-paid {
|
||||
background-color: #E2F0D9 !important; /* Soft Green Match */
|
||||
color: #385723 !important;
|
||||
border: 1px solid rgba(56, 87, 35, 0.15);
|
||||
background-color: rgba(25, 135, 84, 0.06) !important;
|
||||
color: #198754 !important;
|
||||
border: 1px solid rgba(25, 135, 84, 0.15);
|
||||
}
|
||||
|
||||
.pay-pending {
|
||||
background-color: #F8D7DA !important; /* soft red alert tint for unpaid/abandoned items */
|
||||
color: #721C24 !important;
|
||||
border: 1px solid rgba(114, 28, 36, 0.15);
|
||||
background-color: rgba(220, 53, 69, 0.06) !important;
|
||||
color: #dc3545 !important;
|
||||
border: 1px solid rgba(220, 53, 69, 0.15);
|
||||
}
|
||||
|
||||
.pay-refunded {
|
||||
background-color: #E2E3E5 !important;
|
||||
color: #383D41 !important;
|
||||
border: 1px solid rgba(56, 61, 65, 0.15);
|
||||
background-color: rgba(108, 117, 125, 0.08) !important;
|
||||
color: #6c757d !important;
|
||||
border: 1px solid rgba(108, 117, 125, 0.2);
|
||||
}
|
||||
|
||||
/* --- Curated Shipping Identity Panels --- */
|
||||
/* --- Saved Addresses Section Layout --- */
|
||||
.address-curated-card {
|
||||
transition: all 0.23s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.address-curated-card:hover {
|
||||
border-color: #111111 !important;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.03) !important;
|
||||
}
|
||||
|
||||
.border-top-dashed {
|
||||
@@ -142,22 +151,21 @@
|
||||
}
|
||||
|
||||
.btn-action-trigger {
|
||||
transition: color 0.15s ease;
|
||||
padding: 0;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
opacity: 0.75;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-action-trigger:hover:not(.text-danger) {
|
||||
color: #111111 !important;
|
||||
text-decoration: underline;
|
||||
.btn-action-trigger:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.btn-action-trigger:hover.text-danger {
|
||||
color: #A71D2A !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* --- Interactive Form Element Overlays --- */
|
||||
/* --- Interactive Address Form Fields --- */
|
||||
.premium-plaintext-field {
|
||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||
background-color: #FAFAFA;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 6px;
|
||||
padding: 0.65rem 0.85rem;
|
||||
font-size: 0.9rem;
|
||||
@@ -196,40 +204,69 @@
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #666666;
|
||||
transition: color 0.2s ease;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-clean-cancel:hover {
|
||||
color: #111111;
|
||||
}
|
||||
|
||||
.border-y {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.context-clickable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* --- Identity Node Presentation Wrappers --- */
|
||||
/* --- Profile Settings Identity Pillar Elements --- */
|
||||
.profile-hero-banner {
|
||||
background-color: #FAFAFA;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: layoutFadeIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
/* --- CSS Grid Book Item Layout --- */
|
||||
.manifest-grid-wrap {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@keyframes layoutFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(6px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
@media (min-width: 768px) {
|
||||
.manifest-grid-wrap {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.manifest-grid-cell {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.manifest-book-item {
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.manifest-book-item:hover {
|
||||
background-color: rgba(0, 0, 0, 0.015) !important;
|
||||
}
|
||||
|
||||
.book-thumbnail-container {
|
||||
width: 42px;
|
||||
height: 56px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.book-thumbnail-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.shadow-xs {
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.min-w-0 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.order-summary-footer {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06) !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user