From a66a84af755c6928a898a4f720d3337aa12f7ba0 Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Sun, 7 Jun 2026 15:41:54 +0200 Subject: [PATCH] Stable user session management --- .../Components/Layout/MainLayout.razor | 75 +++- .../Components/Layout/MainLayout.razor.cs | 6 + .../Components/Layout/MainLayout.razor.css | 32 ++ .../Components/Pages/Account.razor | 301 ++++++++++++++ .../Components/Pages/Account.razor.css | 147 +++++++ .../Components/Pages/Profile.razor | 377 ------------------ .../Components/Pages/Profile.razor.css | 204 ---------- .../Components/RedirectToLogin.razor | 34 +- .../Components/RedirectToLogin.razor.css | 77 ++++ MidrandBookshop/Components/_Imports.razor | 1 + MidrandBookshop/MidrandBookshop.csproj | 4 +- MidrandBookshop/Program.cs | 5 +- .../Properties/launchSettings.json | 2 +- MidrandBookshop/appsettings.json | 6 +- midrandbooks-uat.yml | 21 +- 15 files changed, 673 insertions(+), 619 deletions(-) create mode 100644 MidrandBookshop/Components/Pages/Account.razor create mode 100644 MidrandBookshop/Components/Pages/Account.razor.css delete mode 100644 MidrandBookshop/Components/Pages/Profile.razor delete mode 100644 MidrandBookshop/Components/Pages/Profile.razor.css create mode 100644 MidrandBookshop/Components/RedirectToLogin.razor.css diff --git a/MidrandBookshop/Components/Layout/MainLayout.razor b/MidrandBookshop/Components/Layout/MainLayout.razor index 49cd15c..a766e28 100644 --- a/MidrandBookshop/Components/Layout/MainLayout.razor +++ b/MidrandBookshop/Components/Layout/MainLayout.razor @@ -162,22 +162,67 @@ } - - - - - - Account - + diff --git a/MidrandBookshop/Components/Layout/MainLayout.razor.cs b/MidrandBookshop/Components/Layout/MainLayout.razor.cs index b0bf51d..db11298 100644 --- a/MidrandBookshop/Components/Layout/MainLayout.razor.cs +++ b/MidrandBookshop/Components/Layout/MainLayout.razor.cs @@ -18,6 +18,12 @@ public partial class MainLayout : IDisposable new CartItem { Id = 3, Title = "Album Architectures, Maputo", Author = "Guedes Archive", Price = 350, Quantity = 1 } }; + private void TriggerHeaderLogout() + { + // Force tear-down of the active client websocket pipeline safely + Navigation.NavigateTo("/logout", forceLoad: true); + } + protected override void OnInitialized() { Navigation.LocationChanged += OnLocationChanged; diff --git a/MidrandBookshop/Components/Layout/MainLayout.razor.css b/MidrandBookshop/Components/Layout/MainLayout.razor.css index 857fed0..d76d83c 100644 --- a/MidrandBookshop/Components/Layout/MainLayout.razor.css +++ b/MidrandBookshop/Components/Layout/MainLayout.razor.css @@ -191,3 +191,35 @@ .scroll-container { scroll-behavior: smooth; } + +.no-caret::after { + display: none !important; +} + +.brand-dropdown-pane { + border-radius: 0px !important; + border: 1px solid rgba(0, 0, 0, 0.15) !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05) !important; + min-width: 190px; + z-index: 1050; +} + + .brand-dropdown-pane .dropdown-item { + font-size: 0.72rem; + letter-spacing: 0.02em; + transition: background-color 0.15s ease, color 0.15s ease; + } + + .brand-dropdown-pane .dropdown-item:hover { + background-color: #1A1A1A !important; + color: #FFFFFF !important; + } + + .brand-dropdown-pane .dropdown-item.text-danger:hover { + background-color: #DC3545 !important; + color: #FFFFFF !important; + } + +.dropdown-header-identity { + user-select: none; +} \ No newline at end of file diff --git a/MidrandBookshop/Components/Pages/Account.razor b/MidrandBookshop/Components/Pages/Account.razor new file mode 100644 index 0000000..0e55d84 --- /dev/null +++ b/MidrandBookshop/Components/Pages/Account.razor @@ -0,0 +1,301 @@ +@page "/account" +@using Microsoft.AspNetCore.Components.Authorization +@inject NavigationManager Navigation +@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) + { + @foreach (var order in orderHistory) + { +
+
+
+
+
+ @order.OrderId +
+
+ @order.OrderDate.ToString("MMM dd, yyyy") +
+
+ + @order.Status + +
+
+ +
+ + @order.ProductTitle + +
+ +
+ + + + Shipping to: @order.ShippingAddressName +
+
+ +
+
+ Total Paid + R @order.Total.ToString("N2") +
+ + +
+
+
+ } + } + else + { +
+ Loading order history... +
+ } +
+
+ +
+
+
Saved Addresses
+ @if (!showAddForm && editingAddress == null) + { + + } +
+ + @if (showAddForm) + { +
+
+
New Address
+ +
+ + +
+ + +
+
+ + +
+
+ +
+
+ } + + @if (editingAddress != null) + { +
+
+
Edit Address
+ +
+ + +
+ + +
+
+ + +
+
+ +
+
+ } + + @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 + +
+
+
+
+
+
+ +@code { + private bool showAddForm = false; + private AddressItem? editingAddress = null; + private string newAddressName = ""; + private string newStreetAddress = ""; + private string newCity = ""; + private string newPostalCode = ""; + private bool isBilling, isShipping; + + 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 } + }; + + 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 } + }; + + private void TriggerLogout() => Navigation.NavigateTo("/logout", forceLoad: true); + private void DownloadInvoice(string orderId) { /* Handle download sequence here */ } + private void OpenAddForm() { editingAddress = null; showAddForm = true; } + + private void SaveAddress() + { + if (!string.IsNullOrWhiteSpace(newAddressName) && !string.IsNullOrWhiteSpace(newStreetAddress)) + { + var nextId = savedAddresses.Any() ? savedAddresses.Max(a => a.Id) + 1 : 1; + savedAddresses.Add(new AddressItem + { + Id = nextId, + Name = newAddressName, + Street = newStreetAddress, + City = newCity, + PostalCode = newPostalCode, + IsBilling = isBilling, + IsShipping = isShipping, + IsPrimary = !savedAddresses.Any() + }); + ResetAddForm(); + } + } + + 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) + { + 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; } + editingAddress = null; + } + } + + 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); } + 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; } } +} \ No newline at end of file diff --git a/MidrandBookshop/Components/Pages/Account.razor.css b/MidrandBookshop/Components/Pages/Account.razor.css new file mode 100644 index 0000000..d7a228f --- /dev/null +++ b/MidrandBookshop/Components/Pages/Account.razor.css @@ -0,0 +1,147 @@ +::deep .container { + max-width: 1100px; +} + +/* Navigation Layout Overrides - Black & White Architectural Style */ +.nav-pills .nav-link { + color: #6c757d; + border-radius: 0; + padding: 0.75rem 1rem; + font-weight: 500; + font-family: var(--bs-body-font-family); + transition: all 0.2s ease-in-out; + 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; + color: #FFFFFF !important; + border-color: #1A1A1A; + } + + /* Hover State for Unselected Buttons */ + .nav-pills .nav-link:hover:not(.active) { + color: #1A1A1A; + background-color: #F8F8F8 !important; + } + + /* Logout Button Link Alignment rules */ + .nav-pills .nav-link.text-danger { + color: #DC3545 !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; +} + +.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-weight: 600; +} + +.status-delivered { + background-color: #E2F0D9 !important; + color: #385723 !important; +} + +.status-shipped { + background-color: #FFF2CC !important; + color: #7F6000 !important; +} + +.status-processing, .status-pending { + background-color: #F2F2F2 !important; + color: #595959 !important; + border: 1px dashed #D9D9D9; +} + +/* Restored Action Button Interactive Properties */ +.action-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 50%; + background: transparent; + border: none; + color: #1A1A1A; + transition: background-color 0.15s ease, transform 0.1s ease; +} + + .action-btn:hover { + background-color: rgba(0, 0, 0, 0.05); + } + + .action-btn:active { + transform: scale(0.92); + } + +.pointer-label { + cursor: pointer; + user-select: none; +} + +.tab-pane #profile .card { + border: 1px dashed rgba(0, 0, 0, 0.15) !important; +} diff --git a/MidrandBookshop/Components/Pages/Profile.razor b/MidrandBookshop/Components/Pages/Profile.razor deleted file mode 100644 index 64165ed..0000000 --- a/MidrandBookshop/Components/Pages/Profile.razor +++ /dev/null @@ -1,377 +0,0 @@ -@page "/profile" -@using Microsoft.AspNetCore.Components.Authorization -@inject NavigationManager Navigation -@rendermode InteractiveServer - - - -
-

My Account

-
-
- -
- -
-
- -
-
-
Order History
-
- -
- @if (orderHistory != null) - { - @foreach (var order in orderHistory) - { -
-
- -
-
-
- @order.OrderId -
-
- @order.OrderDate.ToString("MMM dd, yyyy") -
-
- - @order.Status - -
-
- -
- - @order.ProductTitle - -
- -
- - - - Shipped to: @order.ShippingAddressName -
-
- -
-
- Total Paid - R @order.Total.ToString("N2") -
- - -
- -
-
- } - } - else - { -
- Loading order history... -
- } -
-
- -
-
-
Saved Addresses
- @if (!showAddForm && editingAddress == null) - { - - } -
- - @if (showAddForm) - { -
-
-
New Address
- -
- - -
- - -
-
- - -
-
- -
-
- } - - @if (editingAddress != null) - { -
-
-
Edit Address
- -
- - -
- - -
-
- - -
-
- -
-
- } - - @foreach (var addr in savedAddresses) - { -
-
-
-
@addr.Name
-

@addr.Street, @addr.City, @addr.PostalCode

-
- @if (addr.IsBilling) - { - [Billing] - } - @if (addr.IsShipping) - { - [Shipping] - } -
-
- -
- - - - - -
-
-
- } -
- -
-
-
Profile Settings
-
-
-

Manage your password and profile data here.

-
-
- -
-
-
-
-
- - - - -
- -@code { - private bool showAddForm = false; - private AddressItem? editingAddress = null; - private string newAddressName = ""; - private string newStreetAddress = ""; - private string newCity = ""; - private string newPostalCode = ""; - private bool isBilling, isShipping; - - 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 } - }; - - 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 } - }; - - private void TriggerLogout() => Navigation.NavigateTo("/logout", forceLoad: true); - - private void DownloadInvoice(string orderId) - { - // Handle invoice downloading logic here - } - - private void OpenAddForm() - { - editingAddress = null; - showAddForm = true; - } - - private void SaveAddress() - { - if (!string.IsNullOrWhiteSpace(newAddressName) && !string.IsNullOrWhiteSpace(newStreetAddress)) - { - var nextId = savedAddresses.Any() ? savedAddresses.Max(a => a.Id) + 1 : 1; - var newItem = new AddressItem - { - Id = nextId, - Name = newAddressName, - Street = newStreetAddress, - City = newCity, - PostalCode = newPostalCode, - IsBilling = isBilling, - IsShipping = isShipping, - IsPrimary = !savedAddresses.Any() - }; - savedAddresses.Add(newItem); - ResetAddForm(); - } - } - - private void ResetAddForm() - { - newAddressName = ""; - newStreetAddress = ""; - newCity = ""; - newPostalCode = ""; - isBilling = false; - isShipping = false; - 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 UpdateAddress() - { - if (editingAddress != null) - { - 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; - } - editingAddress = null; - } - } - - private void CancelEditing() - { - editingAddress = null; - } - - 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); - } - } - 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; } - - public string DisplayTitle - { - get - { - if (string.IsNullOrWhiteSpace(ProductTitle)) return ""; - const int maxLength = 21; // Shifted slightly down from 25 to protect bounds against lower resolutions - return ProductTitle.Length <= maxLength - ? ProductTitle - : $"{ProductTitle.Substring(0, maxLength)}..."; - } - } - } -} \ No newline at end of file diff --git a/MidrandBookshop/Components/Pages/Profile.razor.css b/MidrandBookshop/Components/Pages/Profile.razor.css deleted file mode 100644 index 87a100d..0000000 --- a/MidrandBookshop/Components/Pages/Profile.razor.css +++ /dev/null @@ -1,204 +0,0 @@ -::deep .container { - max-width: 1100px; -} - -/* Navigation Layout overrides */ -.nav-pills .nav-link { - color: #6c757d; - border-radius: 0; - padding: 0.75rem 0; - font-weight: 500; - transition: all 0.2s ease; -} - - .nav-pills .nav-link.active { - background-color: transparent !important; - color: #1A1A1A; - border-bottom: 2px solid #1A1A1A; - } - - .nav-pills .nav-link:hover:not(.active) { - color: #1A1A1A; - } - -/* 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); - } - -/* Container Wrapper to Suppress the Scrollbar completely */ -.table-container-fixed { - width: 100%; - overflow-x: hidden; /* Hard disables horizontal scroll bar activation */ -} - -/* Global Table Typography - Reduced uniformly to keep items on a single line */ -.profile-table { - font-size: 0.78rem; /* Scaled down further to eliminate overflow bounds */ - width: 100%; - table-layout: fixed; /* Fixes proportions to fit 100% parent container space */ -} - - .profile-table tbody td { - padding-top: 0.85rem; - padding-bottom: 0.85rem; - } - - .profile-table thead th { - background-color: #F9F9F9; - font-size: 0.7rem; - letter-spacing: 0.04rem; - } - -/* Compact Column Proportions */ -.col-order-id { - width: 115px; -} - -.col-date { - width: 100px; -} - -.col-total { - width: 85px; -} - -.col-status { - width: 105px; -} - -.col-invoice { - width: 65px; -} - -.col-title { - width: auto; /* Takes shared residual space smoothly */ -} - -.col-address { - width: auto; /* Takes shared residual space smoothly */ -} - -/* Product link handling */ -.product-link { - color: #1A1A1A; - text-decoration: none; - border-bottom: 1px dashed transparent; - transition: border-color 0.2s ease; - display: inline-block; -} - - .product-link:hover { - border-color: #1A1A1A; - } - -/* Base Badge Settings */ -.badge { - font-size: 0.62rem; - letter-spacing: 0.5px; - padding: 0.4em 0.8em; - border-radius: 4px; - font-weight: 600; -} - -/* Status Badge Palette Colors */ -.status-shipped { - background-color: #e3f2fd !important; - color: #0d6efd !important; - border: 1px solid rgba(13, 110, 253, 0.15); -} - -.status-delivered { - background-color: #e8f5e9 !important; - color: #198754 !important; - border: 1px solid rgba(25, 135, 84, 0.15); -} - -.badge-tag { - background-color: #f0f0f0 !important; - color: #4a4a4a !important; - border: 1px solid rgba(0, 0, 0, 0.05); -} - -/* Form Buttons */ -.btn-outline-dark { - border-radius: 50px; - border-width: 1px; -} - -.btn-dark { - border-radius: 50px; -} - -/* Action button configurations */ -.action-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - border-radius: 50%; - text-decoration: none; - transition: background-color 0.15s ease, transform 0.1s ease; -} - - .action-btn:hover { - background-color: rgba(0, 0, 0, 0.05); - } - - .action-btn.text-danger:hover { - background-color: rgba(220, 53, 69, 0.1); - } - - .action-btn:active { - transform: scale(0.95); - } - -/* Compact SVG Icons sizing */ -.svg-icon { - width: 15px; - height: 15px; - fill: currentColor; - display: inline-block; - vertical-align: middle; -} - -.pointer-label { - cursor: pointer; - user-select: none; -} - -/* Shared Card Styling Unification Rules */ -.order-history-card { - transition: border-color 0.2s ease, box-shadow 0.2s ease; - border-radius: 0; /* Matches your address card configuration */ -} - - .order-history-card:hover { - border-color: rgba(0, 0, 0, 0.16); - } - -/* Micro Typography Alignment */ -.xx-small { - font-size: 0.65rem; -} - -.tracking-wider { - letter-spacing: 0.05rem; -} - -/* Responsive adjustments across layout break-points */ -@media (max-width: 575.98px) { - .border-sm-top-0 { - border-top: 1px solid rgba(0, 0, 0, 0.06) !important; - } -} \ No newline at end of file diff --git a/MidrandBookshop/Components/RedirectToLogin.razor b/MidrandBookshop/Components/RedirectToLogin.razor index 68ff98c..98fbb0e 100644 --- a/MidrandBookshop/Components/RedirectToLogin.razor +++ b/MidrandBookshop/Components/RedirectToLogin.razor @@ -1,10 +1,42 @@ @inject NavigationManager Navigation +
+
+
+
+ +
+ + + + + + + + + + + + +
+ +
+ +
Secure Connection // Identity Node
+

Verifying Session

+

+ Redirecting you safely to the identity gateway to authorize your access permissions... +

+
+
+
+
+ @code { protected override void OnInitialized() { var returnUrl = Navigation.ToBaseRelativePath(Navigation.Uri); - Navigation.NavigateTo($"/login?redirectUri={Uri.EscapeDataString(returnUrl)}", forceLoad: true); + Navigation.NavigateTo($"/login?returnUrl={Uri.EscapeDataString(returnUrl)}", forceLoad: true); } } \ No newline at end of file diff --git a/MidrandBookshop/Components/RedirectToLogin.razor.css b/MidrandBookshop/Components/RedirectToLogin.razor.css new file mode 100644 index 0000000..79257cd --- /dev/null +++ b/MidrandBookshop/Components/RedirectToLogin.razor.css @@ -0,0 +1,77 @@ +.artistic-redirect-container { + background-color: #ffffff; + min-height: 70vh; + display: flex; + align-items: center; + padding: 5rem 0; +} + +.artistic-svg-wrapper { + display: flex; + justify-content: center; + align-items: center; + position: relative; +} + +.editorial-subheadline { + font-family: 'Playfair Display', serif; + font-size: 2.2rem; + line-height: 1.2; + letter-spacing: -0.01em; + color: #111111; +} + +.body-manifesto-text-center { + font-size: 1rem; + max-width: 360px; + line-height: 1.6; +} + +.meta-tag { + font-size: 0.75rem; + letter-spacing: 0.2em; +} + +/* Custom Minimalist Spinner Concept */ +.editorial-spinner { + width: 40px; + height: 40px; + border: 2px solid #F0F0F0; + border-top: 2px solid #1A1A1A; /* High contrast matching the SVG path line color */ + border-radius: 50%; + animation: spin-editorial 0.8s cubic-bezier(0.4, 0, 0.2, 1) infinite; +} + +/* Subtle background ring pulse paths */ +.animated-pulse-ring { + transform-origin: 200px 180px; + animation: gate-pulse 3s ease-in-out infinite; +} + +.animated-pulse-ring-delayed { + transform-origin: 200px 180px; + animation: gate-pulse 3s ease-in-out infinite; + animation-delay: 1.5s; +} + +@keyframes spin-editorial { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes gate-pulse { + 0%, 100% { + transform: scale(0.95); + opacity: 0.4; + } + + 50% { + transform: scale(1.05); + opacity: 0.9; + } +} diff --git a/MidrandBookshop/Components/_Imports.razor b/MidrandBookshop/Components/_Imports.razor index d925a7f..df7522e 100644 --- a/MidrandBookshop/Components/_Imports.razor +++ b/MidrandBookshop/Components/_Imports.razor @@ -9,3 +9,4 @@ @using MidrandBookshop @using MidrandBookshop.Components @using MidrandBookshop.Components.Layout +@using Microsoft.AspNetCore.Authorization;@using Microsoft.AspNetCore.Components.Authorization \ No newline at end of file diff --git a/MidrandBookshop/MidrandBookshop.csproj b/MidrandBookshop/MidrandBookshop.csproj index f9c4d96..396b5d8 100644 --- a/MidrandBookshop/MidrandBookshop.csproj +++ b/MidrandBookshop/MidrandBookshop.csproj @@ -18,13 +18,13 @@ - + - + diff --git a/MidrandBookshop/Program.cs b/MidrandBookshop/Program.cs index b2257d3..1172d23 100644 --- a/MidrandBookshop/Program.cs +++ b/MidrandBookshop/Program.cs @@ -5,8 +5,6 @@ using Microsoft.AspNetCore.HttpOverrides; using MidrandBookshop.Components; using static LiteCharms.Features.Extensions.Quartz; -AppContext.SetSwitch("Microsoft.IdentityModel.DisableTelemetry", true); - var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents() @@ -16,7 +14,7 @@ builder.AddMonitoring(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddMediator(); -builder.Services.AddAuthentikUiSecurity(builder.Configuration); +builder.Services.AddLiteCharmsWebSecurity(builder.Configuration); builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TelemetryPipelineBehavior<,>)); builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingPipelineBehavior<,>)); @@ -34,7 +32,6 @@ builder.Services.AddMidrandShopDatabase(builder.Configuration); builder.Services.AddMidrandShopPostgresHealthCheck(); builder.Services.AddMidrandShopQuartzHealthCheck(); builder.Services.AddHealthChecksSupport(builder.Configuration); -builder.Services.AddCascadingAuthenticationState(); builder.Services.Configure(options => { diff --git a/MidrandBookshop/Properties/launchSettings.json b/MidrandBookshop/Properties/launchSettings.json index a243441..244d0fb 100644 --- a/MidrandBookshop/Properties/launchSettings.json +++ b/MidrandBookshop/Properties/launchSettings.json @@ -13,7 +13,7 @@ "https": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:7021;http://localhost:5053", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/MidrandBookshop/appsettings.json b/MidrandBookshop/appsettings.json index e102504..2823623 100644 --- a/MidrandBookshop/appsettings.json +++ b/MidrandBookshop/appsettings.json @@ -1,8 +1,6 @@ { - "AuthentikSettings": { - "Authority": "https://id.khongisa.co.za/application/o/midrand-books-uat/", - "MetadataEndpoint": "https://id.khongisa.co.za/application/o/midrand-books-uat/.well-known/openid-configuration", - "RevokationEndpoint": "https://id.khongisa.co.za/application/o/revoke/" + "LiteCharmsSettings": { + "Authority": "https://sts.security.khongisa.co.za" }, "HasherSettings": { "MinHashLength": 11 diff --git a/midrandbooks-uat.yml b/midrandbooks-uat.yml index 9a498a2..7467c23 100644 --- a/midrandbooks-uat.yml +++ b/midrandbooks-uat.yml @@ -26,9 +26,8 @@ data: ValidPayfastHosts__4: "ips.payfast.co.za" ValidPayfastHosts__5: "api.payfast.co.za" ValidPayfastHosts__6: "payment.payfast.io" - AuthentikSettings__Authority: "https://id.khongisa.co.za/application/o/midrand-books-api-uat/" - AuthentikSettings__MetadataEndpoint: "https://id.khongisa.co.za/application/o/midrand-books-uat/.well-known/openid-configuration" - AuthentikSettings__RevokationEndpoint: "https://id.khongisa.co.za/application/o/revoke/" + LiteCharmsSettings__Authority: "https://sts.security.khongisa.co.za" + LiteCharmsSettings__Audience: "midrandbooks-api" ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true" --- apiVersion: v1 @@ -45,8 +44,8 @@ data: hasher-payfastpassphrase: OUdBSVIwdFdwaFgwcU8= bookshop-s3-accesskey: R0s1MTRkMmNlOGRjNjkyMzdhMDVjMDFlZWY= bookshop-s3-secretkey: ZWFhZmVkYTFhZWQ0MDllY2ZlNjA3MTRlY2RhNTQ5YjgyYmRmNWEzZGFmOWYxOGRkNjFmNjZiNDk3M2E2NDgyZQ== - authentik-clientid: Nm9oZk1lSndQNWR0YWY1RFMzZU9MY2NNSHF6WXlma1YzRTNGeE5Tbw== - authentik-clientsecret: TXV2a0FLQklHR3BkdEsyaFlabVU1dFRaUmNuM2FhRzhoMWhlVE1nazFYOGVwczYyMzNCS0REWGdpNXo0T01RalVzMGZEUEFmakpmVVRNN1h3ZjllMU01MTQyVGlvOXRycUdmZTM1THhPaExEUnp6N2gxSm5jVkNLYXZXUllndmQ= + litecharms-clientid: bWlkcmFuZGJvb2tzLXVhdA== + litecharms-clientsecret: c2VjcmV0Xzc3OGJkODM3NWFjNGE3Mzg2N2QxZDdhNjcwODJlZTJjNGU4NmUwODYwYmI0Y2ZlZWI5NDExOTQ5OTk2ZThhOGU= --- apiVersion: v1 kind: PersistentVolumeClaim @@ -99,16 +98,16 @@ spec: - configMapRef: name: midrandbooks-config env: - - name: AuthentikSettings__ClientId + - name: LiteCharmsSettings__ClientId valueFrom: secretKeyRef: - name: midrandbooks-secrets - key: authentik-clientid - - name: AuthentikSettings__ClientSecret + name: midrandbooksapi-secrets + key: litecharms-clientid + - name: LiteCharmsSettings__ClientSecret valueFrom: secretKeyRef: - name: midrandbooks-secrets - key: authentik-clientsecret + name: midrandbooksapi-secrets + key: litecharms-clientsecret - name: BookshopS3Settings__AccessKey valueFrom: secretKeyRef: -- 2.47.3