-
-
+
+
+
+
+
+
+
+
Catalog Circuit Sync
+
+
+
+
+ Connection interrupted. Re-indexing active reading stack...
+
+
+
+ Sync delayed. Re-aligning database archives in 0s...
+
+
+
+ Archival path blocked. Automated sync offline.
+
+
+
+ Reading layout paused by host environment node.
+
+
+
+ State alignment broken.
+
+
+
+
+
+
+
+
-
- Rejoining the server...
-
-
- Rejoin failed... trying again in seconds.
-
-
- Failed to rejoin.
Please retry or reload the page.
-
-
-
- The session has been paused by the server.
-
-
- Failed to resume the session.
Please retry or reload the page.
-
-
-
+
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Layout/ReconnectModal.razor.css b/MidrandBookshop/Components/Layout/ReconnectModal.razor.css
index 3ad3773..53204b6 100644
--- a/MidrandBookshop/Components/Layout/ReconnectModal.razor.css
+++ b/MidrandBookshop/Components/Layout/ReconnectModal.razor.css
@@ -1,157 +1,218 @@
-.components-reconnect-first-attempt-visible,
-.components-reconnect-repeated-attempt-visible,
-.components-reconnect-failed-visible,
-.components-pause-visible,
-.components-resume-failed-visible,
-.components-rejoining-animation {
- display: none;
-}
-
-#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
-#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
-#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
-#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
-#components-reconnect-modal.components-reconnect-retrying,
-#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
-#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
-#components-reconnect-modal.components-reconnect-failed,
-#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
- display: block;
-}
-
+/* ==========================================================================
+ Midrand Books — Glassine Architectural Veil & Ribbon Strip
+ ========================================================================== */
+/* --- Native Dialog Element Layout Overrides --- */
#components-reconnect-modal {
- background-color: white;
- width: 20rem;
- margin: 20vh auto;
- padding: 2rem;
- border: 0;
- border-radius: 0.5rem;
- box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
- opacity: 0;
- transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
- animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
- &[open]
-
-{
- animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
- animation-fill-mode: both;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ max-width: 100vw;
+ max-height: 100vh;
+ margin: 0;
+ padding: 0;
+ border: none;
+ background: transparent;
+ z-index: 99999;
+ overflow: hidden;
}
-}
-
-#components-reconnect-modal::backdrop {
- background-color: rgba(0, 0, 0, 0.4);
- animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
- opacity: 1;
-}
-
-@keyframes components-reconnect-modal-slideUp {
- 0% {
- transform: translateY(30px) scale(0.95);
+ /* Remove default browser modal backdrop blockout to allow custom layering below */
+ #components-reconnect-modal::backdrop {
+ background: transparent;
}
- 100% {
+/* --- Glassine Page Jacket Layer ---
+ Frosted translucent shield that preserves context visibility while blocking mouse actions
+*/
+.glassine-page-jacket {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(251, 251, 250, 0.4); /* Premium warm paper tint */
+ backdrop-filter: blur(5px); /* Elegant frosted glass sweep */
+ cursor: wait;
+ animation: glassFadeIn 0.35s ease-out forwards;
+}
+
+@keyframes glassFadeIn {
+ from {
+ opacity: 0;
+ backdrop-filter: blur(0px);
+ }
+
+ to {
+ opacity: 1;
+ backdrop-filter: blur(5px);
+ }
+}
+
+/* --- The Sliding Ribbon Banner --- */
+.literary-sync-strip {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ background-color: #FFFFFF;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05), 0 1px 3px rgba(0, 0, 0, 0.02);
+ padding: 1.1rem 2rem;
+ box-sizing: border-box;
+ z-index: 2;
+ /* Animation kinematics: smooth physical drop slide */
+ transform: translateY(-100%);
+ animation: stripSlideDown 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+@keyframes stripSlideDown {
+ to {
transform: translateY(0);
}
}
-@keyframes components-reconnect-modal-fadeInOpacity {
- 0% {
- opacity: 0;
- }
-
- 100% {
- opacity: 1;
- }
-}
-
-@keyframes components-reconnect-modal-fadeOutOpacity {
- 0% {
- opacity: 1;
- }
-
- 100% {
- opacity: 0;
- }
-}
-
-.components-reconnect-container {
- display: flex;
- flex-direction: column;
+/* --- Ribbon Layout Matrix Grid --- */
+.strip-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ display: grid;
+ grid-template-columns: auto 1fr auto;
align-items: center;
- gap: 1rem;
+ gap: 2.5rem;
}
-#components-reconnect-modal p {
- margin: 0;
- text-align: center;
+/* Left Node Ticker Elements & Animated Vector */
+.sync-status-indicator {
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ border-right: 1px solid rgba(0, 0, 0, 0.08);
+ padding-right: 2rem;
}
-#components-reconnect-modal button {
- border: 0;
- background-color: #6b9ed2;
- color: white;
- padding: 4px 24px;
- border-radius: 4px;
+.small-tracking {
+ font-family: var(--bs-font-monospace);
+ font-size: 0.7rem;
+ letter-spacing: 0.12em;
+ color: #111111;
+ font-weight: 600;
}
- #components-reconnect-modal button:hover {
- background-color: #3b6ea2;
- }
-
- #components-reconnect-modal button:active {
- background-color: #6b9ed2;
- }
-
-.components-rejoining-animation {
- position: relative;
- width: 80px;
- height: 80px;
+/* Animated Book Helix SVG */
+.literary-helix-loader {
+ width: 18px;
+ height: 18px;
+ color: #111111;
}
- .components-rejoining-animation div {
- position: absolute;
- border: 3px solid #0087ff;
- opacity: 1;
- border-radius: 50%;
- animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
- }
+.flipping-leaf-vector {
+ transform-origin: 12px 12px;
+ animation: svgLeafFlip 1.6s infinite cubic-bezier(0.4, 0, 0.2, 1);
+}
- .components-rejoining-animation div:nth-child(2) {
- animation-delay: -0.5s;
- }
-
-@keyframes components-rejoining-animation {
+@keyframes svgLeafFlip {
0% {
- top: 40px;
- left: 40px;
- width: 0;
- height: 0;
- opacity: 0;
- }
-
- 4.9% {
- top: 40px;
- left: 40px;
- width: 0;
- height: 0;
- opacity: 0;
- }
-
- 5% {
- top: 40px;
- left: 40px;
- width: 0;
- height: 0;
+ transform: scaleX(1);
opacity: 1;
}
+ 50% {
+ transform: scaleX(0);
+ opacity: 0.3;
+ }
+
100% {
- top: 0px;
- left: 0px;
- width: 80px;
- height: 80px;
+ transform: scaleX(-1);
opacity: 0;
}
}
+
+/* Center Node Text Content */
+.sync-message-body {
+ font-family: Georgia, 'Times New Roman', serif;
+ font-size: 0.95rem;
+ color: #333333;
+ font-style: italic;
+}
+
+.text-crimson {
+ color: #A34843;
+ font-style: normal;
+ font-weight: 500;
+}
+
+/* Right Node Fine-Press Button Trigger */
+.btn-strip-action {
+ background: #111111;
+ color: #FFFFFF;
+ border: 1px solid #111111;
+ padding: 0.45rem 1.25rem;
+ font-family: var(--bs-font-monospace);
+ font-size: 0.7rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ border-radius: 4px;
+ transition: all 0.2s ease;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+ .btn-strip-action:hover {
+ background: transparent;
+ color: #111111;
+ }
+
+ .btn-strip-action svg {
+ transition: transform 0.2s ease;
+ }
+
+ .btn-strip-action:hover svg {
+ transform: rotate(45deg);
+ }
+
+/* --- Display Mechanics Matrix Controllers --- */
+.components-reconnect-first-attempt-visible,
+.components-reconnect-repeated-attempt-visible,
+.components-reconnect-failed-visible,
+.components-pause-visible,
+.components-resume-failed-visible {
+ display: none !important;
+}
+
+#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
+#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
+#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
+#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
+#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
+ display: inline-block !important;
+}
+
+#components-reconnect-modal.components-reconnect-failed .btn-strip-action,
+#components-reconnect-modal.components-reconnect-paused .btn-strip-action,
+#components-reconnect-modal.components-reconnect-resume-failed .btn-strip-action {
+ display: inline-flex !important;
+}
+
+/* Tablet Parameters Response Collapse Matrix */
+@media (max-width: 768px) {
+ .strip-container {
+ grid-template-columns: 1fr;
+ gap: 0.65rem;
+ text-align: center;
+ }
+
+ .sync-status-indicator {
+ border-right: none;
+ padding-right: 0;
+ justify-content: center;
+ }
+
+ .sync-action-node {
+ margin-top: 0.25rem;
+ }
+}
diff --git a/MidrandBookshop/Components/Layout/ReconnectModal.razor.js b/MidrandBookshop/Components/Layout/ReconnectModal.razor.js
index a44de78..bf38930 100644
--- a/MidrandBookshop/Components/Layout/ReconnectModal.razor.js
+++ b/MidrandBookshop/Components/Layout/ReconnectModal.razor.js
@@ -1,4 +1,3 @@
-// Set up event handlers
const reconnectModal = document.getElementById("components-reconnect-modal");
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
@@ -24,14 +23,8 @@ async function retry() {
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
try {
- // Reconnect will asynchronously return:
- // - true to mean success
- // - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID)
- // - exception to mean we didn't reach the server (this can be sync or async)
const successful = await Blazor.reconnect();
if (!successful) {
- // We have been able to reach the server, but the circuit is no longer available.
- // We'll reload the page so the user can continue using the app as quickly as possible.
const resumeSuccessful = await Blazor.resumeCircuit();
if (!resumeSuccessful) {
location.reload();
@@ -40,7 +33,6 @@ async function retry() {
}
}
} catch (err) {
- // We got an exception, server is currently unavailable
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
}
}
@@ -60,4 +52,4 @@ async function retryWhenDocumentBecomesVisible() {
if (document.visibilityState === "visible") {
await retry();
}
-}
+}
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/About.razor b/MidrandBookshop/Components/Pages/About.razor
new file mode 100644
index 0000000..ef48fb7
--- /dev/null
+++ b/MidrandBookshop/Components/Pages/About.razor
@@ -0,0 +1,68 @@
+@page "/about"
+@rendermode InteractiveServer
+@inject IJSRuntime JSRuntime
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1. A Curated Space for Readers
+
+ At Midrand Books, we believe that an online bookstore should feel just as warm, inspiring, and intentional as a physical corner shop. We aren’t interested in mass-market commercial algorithms; we are interested in books that leave a mark.
+
+
+ Operated proudly under Lite Charms (Pty) Ltd, our storefront is designed to showcase beautiful storytelling, critical histories, academic research, and deep technical disciplines. We source fine press editions and trusted literary brands, ensuring that every book we package and deliver across South Africa feels special from the moment it reaches your hands.
+
+
+
+
+ 2. Empowering New & Independent Voices
+
+ Beyond our role as a bookseller, our truest passion lies in cultivating the next chapter of South African literature. We know how daunting the modern publishing landscape can be for emerging storytellers, experts, and independent creators.
+
+
+ That is why we have integrated custom distribution channels into our platform to help self-publishers and local authors get their manuscripts beautifully styled, correctly indexed, and directly in front of avid readers. Side-by-side with heritage publishing houses, we champion the creative freedom of the indie writer.
+
+
+
+
+
+
+
+
+
+
+@code {
+ private async Task ScrollToSection(string elementId)
+ {
+ await JSRuntime.InvokeVoidAsync("eval", $@"
+ var el = document.getElementById('{elementId}');
+ if (el) {{
+ el.scrollIntoView({{ behavior: 'smooth', block: 'start' }});
+ }}
+ ");
+ }
+}
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/About.razor.css b/MidrandBookshop/Components/Pages/About.razor.css
new file mode 100644
index 0000000..efaaee3
--- /dev/null
+++ b/MidrandBookshop/Components/Pages/About.razor.css
@@ -0,0 +1,85 @@
+/* ==========================================================================
+ Midrand Books — About Page Fine Press Styles
+ ========================================================================== */
+
+.editorial-page-container {
+ max-width: 1140px;
+ margin: 0 auto;
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ font-family: system-ui, -apple-system, sans-serif;
+}
+
+.editorial-main-title {
+ font-size: 2.5rem;
+ letter-spacing: -0.03em;
+ color: #111111;
+ font-family: Georgia, 'Times New Roman', serif;
+}
+
+.tracking-wider {
+ letter-spacing: 0.12em;
+}
+
+/* --- Index Navigation Controls --- */
+.editorial-nav-index {
+ position: sticky;
+ top: 2rem;
+ border-left: 1px solid rgba(0, 0, 0, 0.08);
+ padding-left: 1.25rem;
+}
+
+.index-btn {
+ color: #666666;
+ background: transparent;
+ border: none;
+ padding: 0;
+ margin: 0;
+ font-family: inherit;
+ font-size: inherit;
+ cursor: pointer;
+ text-decoration: none;
+ transition: color 0.2s ease, padding-left 0.2s ease;
+}
+
+ .index-btn:hover {
+ color: #111111;
+ padding-left: 4px;
+ }
+
+ .index-btn:focus {
+ outline: none;
+ color: #111111;
+ font-weight: 600;
+ }
+
+/* --- Narrative Typography --- */
+.editorial-article-body {
+ line-height: 1.8;
+ color: #333333;
+ font-size: 1rem;
+}
+
+.section-title {
+ font-family: Georgia, 'Times New Roman', serif;
+ font-size: 1.35rem;
+ color: #111111;
+ margin-bottom: 1.25rem;
+ font-weight: 500;
+ scroll-margin-top: 2rem;
+}
+
+.text-muted-serif {
+ color: #555555;
+}
+
+.editorial-section p {
+ margin-bottom: 1.2rem;
+}
+
+.protective-credo-callout {
+ background-color: #FAFAFA;
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ padding: 2rem;
+ border-radius: 8px;
+}
diff --git a/MidrandBookshop/Components/Pages/Account.razor b/MidrandBookshop/Components/Pages/Account.razor
index b8dbb6a..ab816d7 100644
--- a/MidrandBookshop/Components/Pages/Account.razor
+++ b/MidrandBookshop/Components/Pages/Account.razor
@@ -4,225 +4,237 @@
@rendermode InteractiveServer
@attribute [Authorize]
-
-
My Account
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
-
Customer Profile // Active Session
-
Welcome back, @(context.User.FindFirst("given_name")?.Value ?? context.User.Identity?.Name ?? "Reader")!
-
@context.User.FindFirst("email")?.Value
+
+
+
+
+
Order History
+
+ @if (orderHistory == null || !orderHistory.Any())
+ {
+
+
You haven't placed any orders with us yet.
+
+ }
+ else
+ {
+
+ @foreach (var order in orderHistory)
+ {
+
+
+
+
@order.OrderId
+
Ordered on @order.OrderDate.ToString("dd MMMM yyyy")
+
+
+ Shipped to: @order.ShippingAddressName
+
+
+
+
+
+ Pay: @order.PaymentStatus
+
+
+ Logistics: @order.Status
+
+
+
+ @if (order.PaymentStatus?.ToLower() == "paid")
+ {
+
+ }
+
+
+
+
+
+
@order.ProductTitle
+
+
+ R @order.Total.ToString("F2")
+
+
+
+
+ }
+
+ }
+
-
-
+
+
+
+
Saved Addresses
+ @if (!showAddForm && editingAddress == null)
+ {
+
+ }
+
+
+ @if (showAddForm || editingAddress != null)
+ {
+
+ }
+
+
+ @foreach (var addr in savedAddresses)
+ {
+
+
+
+
@addr.Name
+
+ SetPrimary(addr, e)" />
+
+
+
+
+
+ @addr.Street
+ @addr.City
+
+
+
+
+ @if (addr.IsBilling)
+ {
+
+ }
+ @if (addr.IsShipping)
+ {
+
+ }
+
+
+
+
+
+
+
+
+ }
+
+
+
+
+
+
Profile Settings
+
+
+
+
+
@User?.Identity?.Name
+
+
+
+ Verified
+
+
+
+
+
+
Centralized Identity Node Settings
+
+ 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.
+
+
+
+ Access Central Security Center
+
+
+
+
+
+
-
-
-
-
-
Order History
-
-
-
- @if (orderHistory != null)
- {
- @foreach (var order in orderHistory)
- {
-
-
-
-
-
-
-
-
-
-
Shipping to: @order.ShippingAddressName
-
-
-
-
-
- Total Paid
- R @order.Total.ToString("N2")
-
-
-
-
-
-
- }
- }
- else
- {
-
- Loading order history...
-
- }
-
-
-
-
-
-
Saved Addresses
- @if (!showAddForm && editingAddress == null)
- {
-
- }
-
-
- @if (showAddForm)
- {
-
- }
-
- @if (editingAddress != null)
- {
-
- }
-
- @foreach (var addr in savedAddresses)
- {
-
-
-
-
@addr.Name
-
@addr.Street, @addr.City, @addr.PostalCode
-
- @if (addr.IsBilling)
- {
- [Billing]
- }
- @if (addr.IsShipping)
- {
- [Shipping]
- }
-
-
-
-
-
-
-
-
-
-
-
-
- }
-
-
-
-
-
Profile Settings
-
-
-
-
Centralized Identity Management
-
- For your protection, password modifications, recovery settings, authentication methods, and core credentials are managed through our secure Identity Node.
-
-
- Access Security Center
-
-
-
-
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/Account.razor.cs b/MidrandBookshop/Components/Pages/Account.razor.cs
index 52416b7..7f20cfe 100644
--- a/MidrandBookshop/Components/Pages/Account.razor.cs
+++ b/MidrandBookshop/Components/Pages/Account.razor.cs
@@ -1,7 +1,10 @@
namespace MidrandBookshop.Components.Pages;
-public partial class Account
+public partial class Account : ComponentBase
{
+ [Inject] private AuthenticationStateProvider AuthStateProvider { get; set; } = default!;
+
+ private ClaimsPrincipal? User { get; set; }
private bool showAddForm = false;
private AddressItem? editingAddress = null;
private string newAddressName = "";
@@ -12,27 +15,84 @@ public partial class Account
private List orderHistory = new()
{
- 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", Total = 720.00 },
- 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 Books Warehouse", Status = "Delivered", Total = 890.00 }
+ // 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 savedAddresses = new()
{
new AddressItem { Id = 1, Name = "Home Address", Street = "12 Main Road", City = "Midrand", PostalCode = "1685", IsBilling = true, IsShipping = true, IsPrimary = true },
- new AddressItem { Id = 2, Name = "Corporate Office", Street = "45 Challink Street", City = "Halfway House", PostalCode = "1682", IsBilling = true, IsShipping = false, IsPrimary = false },
- new AddressItem { Id = 3, Name = "Midrand Books Warehouse", Street = "Unit 8, Corporate Park North", City = "Randjespark", PostalCode = "1683", IsBilling = false, IsShipping = true, IsPrimary = false }
+ new AddressItem { Id = 2, Name = "Midrand Warehouse", Street = "Corner of Church & Third Roads", City = "Midrand", PostalCode = "1685", IsBilling = false, IsShipping = false, IsPrimary = false }
};
- private void TriggerLogout() => Navigation.NavigateTo("/logout", forceLoad: true);
- private void DownloadInvoice(string orderId) { /* Handle download sequence here */ }
- private void OpenAddForm() { editingAddress = null; showAddForm = true; }
+ protected override async Task OnInitializedAsync()
+ {
+ var authState = await AuthStateProvider.GetAuthenticationStateAsync();
+ User = authState?.User;
+ }
+
+ private void DownloadInvoice(string orderId)
+ {
+ Navigation.NavigateTo($"/api/invoices/download/{orderId.Replace("#", "")}", forceLoad: true);
+ }
+
+ private string GetStatusClass(string status) => status?.ToLower() switch
+ {
+ "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;
+ }
+
+ private void CancelAddressActions()
+ {
+ showAddForm = false;
+ editingAddress = null;
+ ClearFormFields();
+ }
+
+ private void ClearFormFields()
+ {
+ newAddressName = "";
+ newStreetAddress = "";
+ newCity = "";
+ newPostalCode = "";
+ isBilling = false;
+ isShipping = false;
+ }
private void SaveAddress()
{
- if (!string.IsNullOrWhiteSpace(newAddressName) && !string.IsNullOrWhiteSpace(newStreetAddress))
+ if (string.IsNullOrWhiteSpace(newAddressName) || string.IsNullOrWhiteSpace(newStreetAddress)) return;
+
+ if (editingAddress == null)
{
var nextId = savedAddresses.Any() ? savedAddresses.Max(a => a.Id) + 1 : 1;
- savedAddresses.Add(new AddressItem
+ var newAddr = new AddressItem
{
Id = nextId,
Name = newAddressName,
@@ -42,34 +102,71 @@ public partial class Account
IsBilling = isBilling,
IsShipping = isShipping,
IsPrimary = !savedAddresses.Any()
- });
- ResetAddForm();
+ };
+ savedAddresses.Add(newAddr);
}
- }
-
- private void ResetAddForm() { newAddressName = ""; newStreetAddress = ""; newCity = ""; newPostalCode = ""; isBilling = isShipping = showAddForm = false; }
- private void StartEditing(AddressItem addr) { showAddForm = false; editingAddress = new AddressItem { Id = addr.Id, Name = addr.Name, Street = addr.Street, City = addr.City, PostalCode = addr.PostalCode, IsBilling = addr.IsBilling, IsShipping = addr.IsShipping, IsPrimary = addr.IsPrimary }; }
- private void CancelEditing() => editingAddress = null;
-
- private void UpdateAddress()
- {
- if (editingAddress != null)
+ else
{
var target = savedAddresses.FirstOrDefault(a => a.Id == editingAddress.Id);
- if (target != null) { target.Name = editingAddress.Name; target.Street = editingAddress.Street; target.City = editingAddress.City; target.PostalCode = editingAddress.PostalCode; target.IsBilling = editingAddress.IsBilling; target.IsShipping = editingAddress.IsShipping; }
+ if (target != null)
+ {
+ target.Name = newAddressName;
+ target.Street = newStreetAddress;
+ target.City = newCity;
+ target.PostalCode = newPostalCode;
+ target.IsBilling = isBilling;
+ target.IsShipping = isShipping;
+ }
editingAddress = null;
}
+
+ showAddForm = false;
+ ClearFormFields();
}
- private void DeleteAddress(AddressItem addr) { if (editingAddress?.Id == addr.Id) editingAddress = null; savedAddresses.Remove(addr); if (addr.IsPrimary && savedAddresses.Any()) savedAddresses.First().IsPrimary = true; }
+ private void DeleteAddress(AddressItem addr)
+ {
+ if (editingAddress?.Id == addr.Id) editingAddress = null;
+ savedAddresses.Remove(addr);
+ if (addr.IsPrimary && savedAddresses.Any()) savedAddresses.First().IsPrimary = true;
+ }
private void SetPrimary(AddressItem target, ChangeEventArgs e)
{
var isChecked = (bool)(e.Value ?? false);
- if (isChecked) { foreach (var addr in savedAddresses) addr.IsPrimary = (addr.Id == target.Id); }
+ if (isChecked)
+ {
+ foreach (var addr in savedAddresses) addr.IsPrimary = (addr.Id == target.Id);
+ }
else target.IsPrimary = false;
}
- public class AddressItem { public int Id { get; set; } public string Name { get; set; } = ""; public string Street { get; set; } = ""; public string City { get; set; } = ""; public string PostalCode { get; set; } = ""; public bool IsBilling { get; set; } public bool IsShipping { get; set; } public bool IsPrimary { get; set; } }
- 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 double Total { get; set; } }
-}
+ private void TriggerLogout()
+ {
+ Navigation.NavigateTo("/logout", forceLoad: true);
+ }
+
+ public class AddressItem
+ {
+ public int Id { get; set; }
+ public string Name { get; set; } = "";
+ public string Street { get; set; } = "";
+ public string City { get; set; } = "";
+ public string PostalCode { get; set; } = "";
+ public bool IsBilling { get; set; }
+ public bool IsShipping { get; set; }
+ public bool IsPrimary { get; set; }
+ }
+
+ 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; }
+ }
+}
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/Account.razor.css b/MidrandBookshop/Components/Pages/Account.razor.css
index d7a228f..1e5414d 100644
--- a/MidrandBookshop/Components/Pages/Account.razor.css
+++ b/MidrandBookshop/Components/Pages/Account.razor.css
@@ -1,147 +1,235 @@
-::deep .container {
- max-width: 1100px;
+/* ==========================================================================
+ Curated Architecture Dashboard Style Matrix
+ ========================================================================== */
+
+.account-page-container {
+ max-width: 1140px;
+ margin: 0 auto;
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ font-family: system-ui, -apple-system, sans-serif;
}
-/* Navigation Layout Overrides - Black & White Architectural Style */
-.nav-pills .nav-link {
- color: #6c757d;
- border-radius: 0;
- padding: 0.75rem 1rem;
+.account-main-title {
+ font-size: 2.25rem;
+ letter-spacing: -0.03em;
+ color: #111111;
+}
+
+.tracking-wider {
+ letter-spacing: 0.08em;
+}
+
+.extra-small {
+ font-size: 0.72rem !important;
+}
+
+/* --- Left Sidebar Architectural Pillar Controls --- */
+.account-nav-stack .nav-link {
+ color: #555555;
+ border-radius: 8px;
+ padding: 0.8rem 1rem;
font-weight: 500;
- font-family: var(--bs-body-font-family);
- transition: all 0.2s ease-in-out;
+ font-size: 0.9rem;
+ transition: all 0.2s ease;
border: 1px solid transparent;
background: transparent !important;
}
- /* Active State - Solid Black Fill with stark white text */
- .nav-pills .nav-link.active {
- background-color: #1A1A1A !important;
+ .account-nav-stack .nav-link.active {
+ background-color: #111111 !important;
color: #FFFFFF !important;
- border-color: #1A1A1A;
+ font-weight: 600;
}
- /* Hover State for Unselected Buttons */
- .nav-pills .nav-link:hover:not(.active) {
- color: #1A1A1A;
- background-color: #F8F8F8 !important;
+ .account-nav-stack .nav-link:hover:not(.active):not(.nav-logout) {
+ color: #111111;
+ background-color: rgba(0, 0, 0, 0.04) !important;
+ transform: translateX(2px);
}
- /* Logout Button Link Alignment rules */
- .nav-pills .nav-link.text-danger {
- color: #DC3545 !important;
+.account-nav-stack .nav-logout:hover {
+ background-color: #FFF5F5 !important;
+ color: #DC3545 !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;
}
- .nav-pills .nav-link.text-danger:hover {
- background-color: #FFF5F5 !important;
- color: #A94442 !important;
- }
-
-hr {
- border-top: 1px solid rgba(0, 0, 0, 0.08);
- margin: 1.5rem 0;
-}
-
-/* Profile Banner Design Definitions */
-.profile-hero-banner {
- border-color: rgba(0, 0, 0, 0.05) !important;
- background-color: #FAFAFA !important;
-}
-
-.brand-greeting {
- font-family: 'Playfair Display', serif;
- font-size: 1.8rem;
- letter-spacing: -0.01em;
- color: #111111;
-}
-
-.meta-tag {
- font-size: 0.68rem;
- letter-spacing: 0.15em;
-}
-
-.hero-crest-svg svg {
- transition: transform 0.4s ease-in-out;
-}
-
-.profile-hero-banner:hover .hero-crest-svg svg {
- transform: rotate(15deg);
-}
-
-/* Cards Layout Rules */
-.card {
- border: 1px solid rgba(0, 0, 0, 0.08);
- border-radius: 0;
-}
-
-.address-card {
- transition: border-color 0.2s ease, box-shadow 0.2s ease;
-}
-
- .address-card:hover {
- border-color: rgba(0, 0, 0, 0.16);
- }
-
-/* Order Meta Tracks & Status Badges Setup */
-.order-meta-track {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 0.75rem;
+.border-b-dashed {
+ border-bottom: 1px dashed rgba(0, 0, 0, 0.08);
}
+/* Minimalist Curated Logistics and Payment Badges Matrix */
.status-badge-base {
font-family: var(--bs-font-monospace);
- font-size: 0.68rem !important;
- letter-spacing: 0.08em;
- padding: 0.35rem 0.65rem !important;
- border-radius: 0px !important;
+ font-size: 0.65rem !important;
+ letter-spacing: 0.05em;
+ padding: 0.3rem 0.6rem !important;
+ border-radius: 4px !important;
font-weight: 600;
}
+/* Fulfillment Matrix Colors */
.status-delivered {
- background-color: #E2F0D9 !important;
+ background-color: #E2F0D9 !important; /* Soft Green Match */
color: #385723 !important;
+ border: 1px solid rgba(56, 87, 35, 0.15);
}
.status-shipped {
- background-color: #FFF2CC !important;
- color: #7F6000 !important;
+ background-color: #FFF3CD !important; /* Warm Gold Amber */
+ color: #856404 !important;
+ border: 1px solid rgba(133, 100, 4, 0.12);
}
-.status-processing, .status-pending {
- background-color: #F2F2F2 !important;
+.status-processing {
+ background-color: #F2F2F2 !important; /* Architectural Neutral Muted Grey */
color: #595959 !important;
- border: 1px dashed #D9D9D9;
+ border: 1px dashed rgba(0, 0, 0, 0.12);
}
-/* Restored Action Button Interactive Properties */
-.action-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- border-radius: 50%;
+/* Financial Matrix Colors */
+.pay-paid {
+ background-color: #E2F0D9 !important; /* Soft Green Match */
+ color: #385723 !important;
+ border: 1px solid rgba(56, 87, 35, 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);
+}
+
+.pay-refunded {
+ background-color: #E2E3E5 !important;
+ color: #383D41 !important;
+ border: 1px solid rgba(56, 61, 65, 0.15);
+}
+
+/* --- Curated Shipping Identity Panels --- */
+.address-curated-card {
+ transition: all 0.23s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+ .address-curated-card:hover {
+ border-color: #111111 !important;
+ transform: translateY(-2px);
+ }
+
+.border-top-dashed {
+ border-top: 1px dashed rgba(0, 0, 0, 0.08);
+}
+
+.btn-action-trigger {
+ transition: color 0.15s ease;
+}
+
+ .btn-action-trigger:hover:not(.text-danger) {
+ color: #111111 !important;
+ text-decoration: underline;
+ }
+
+ .btn-action-trigger:hover.text-danger {
+ color: #A71D2A !important;
+ text-decoration: underline;
+ }
+
+/* --- Interactive Form Element Overlays --- */
+.premium-plaintext-field {
+ border: 1px solid rgba(0, 0, 0, 0.12);
+ border-radius: 6px;
+ padding: 0.65rem 0.85rem;
+ font-size: 0.9rem;
+ color: #111111;
+ transition: all 0.2s ease;
+}
+
+ .premium-plaintext-field:focus {
+ border-color: #111111;
+ box-shadow: 0 0 0 1px #111111;
+ background-color: #FFFFFF;
+ }
+
+.custom-box-tick {
+ border: 1.5px solid rgba(0, 0, 0, 0.25);
+ width: 1rem;
+ height: 1rem;
+ border-radius: 4px;
+ transition: all 0.15s ease;
+ cursor: pointer;
+}
+
+ .custom-box-tick:checked {
+ background-color: #111111;
+ border-color: #111111;
+ }
+
+.btn-premium-sm {
+ font-size: 0.75rem;
+ padding: 0.45rem 1rem;
+ border-radius: 6px;
+ font-weight: 500;
+}
+
+.btn-clean-cancel {
background: transparent;
border: none;
- color: #1A1A1A;
- transition: background-color 0.15s ease, transform 0.1s ease;
+ color: #666666;
+ transition: color 0.2s ease;
}
- .action-btn:hover {
- background-color: rgba(0, 0, 0, 0.05);
+ .btn-clean-cancel:hover {
+ color: #111111;
}
- .action-btn:active {
- transform: scale(0.92);
- }
+.border-y {
+ border-top: 1px solid rgba(0, 0, 0, 0.06);
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+}
-.pointer-label {
+.context-clickable {
cursor: pointer;
user-select: none;
}
-.tab-pane #profile .card {
- border: 1px dashed rgba(0, 0, 0, 0.15) !important;
+/* --- Identity Node Presentation Wrappers --- */
+.profile-hero-banner {
+ 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;
+}
+
+@keyframes layoutFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(6px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
diff --git a/MidrandBookshop/Components/Pages/Checkout.razor b/MidrandBookshop/Components/Pages/Checkout.razor
index ca2317b..68ee92e 100644
--- a/MidrandBookshop/Components/Pages/Checkout.razor
+++ b/MidrandBookshop/Components/Pages/Checkout.razor
@@ -5,7 +5,6 @@
-
@if (IsProcessing)
{
@@ -35,15 +34,30 @@
}
-
-