From 02294d36e84c92011c37526e595cd89bad3ce5a4 Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Tue, 16 Jun 2026 14:37:44 +0200 Subject: [PATCH 1/2] Implemented AddToCart functionality on home page --- MidrandBookshop/Components/BookCard.razor | 31 +++-- MidrandBookshop/Components/BookCard.razor.cs | 32 ++++- .../Components/Layout/MainLayout.razor | 4 +- MidrandBookshop/Setup.cs | 2 + MidrandBookshop/wwwroot/app.css | 110 ++++++++++++++++++ 5 files changed, 170 insertions(+), 9 deletions(-) diff --git a/MidrandBookshop/Components/BookCard.razor b/MidrandBookshop/Components/BookCard.razor index 4b7591f..b81a22a 100644 --- a/MidrandBookshop/Components/BookCard.razor +++ b/MidrandBookshop/Components/BookCard.razor @@ -14,12 +14,29 @@ {
} - + +
+ + + +
@@ -49,4 +66,4 @@
- + \ No newline at end of file diff --git a/MidrandBookshop/Components/BookCard.razor.cs b/MidrandBookshop/Components/BookCard.razor.cs index 0d90da3..239b108 100644 --- a/MidrandBookshop/Components/BookCard.razor.cs +++ b/MidrandBookshop/Components/BookCard.razor.cs @@ -1,4 +1,9 @@ -namespace MidrandBookshop.Components; +using LiteCharms.Features.MidrandBooks.AuthorBooks; +using LiteCharms.Features.MidrandBooks.Authors; +using LiteCharms.Features.MidrandBooks.Payments; +using LiteCharms.Features.MidrandBooks.Products; + +namespace MidrandBookshop.Components; public partial class BookCard { @@ -11,4 +16,29 @@ public partial class BookCard [Parameter] public string BookImageUrl { get; set; } = string.Empty; [Parameter] public EventCallback OnCardClick { get; set; } + + [Inject] private CartService CartService { get; set; } = default!; + [Inject] private ProductService ProductService { get; set; } = default!; + [Inject] private AuthorService AuthorService { get; set; } = default!; + [Inject] private BooksService BooksService { get; set; } = default!; + [Inject] private IToastService ToastService { get; set; } = default!; + [Inject] private CancellationToken CancellationToken { get; set; } = default!; + + private async Task HandleAddToCart() + { + try + { + var bookFetch = await BooksService.GetBookByProductIdAsync(Id, CancellationToken); + var authorFetch = await AuthorService.GetAuthorAsync(bookFetch.Value.AuthorId, CancellationToken); + var productPriceFetch = await ProductService.GetProductPriceAsync(Id, CancellationToken); + + CartService.AddItem(productPriceFetch.Value, bookFetch.Value.Product!, authorFetch.Value); + + ToastService.ShowSuccess($"Added '{Title}' to your order.", "Add To Cart"); + } + catch + { + ToastService.ShowError("Could not update cart. Please try again."); + } + } } diff --git a/MidrandBookshop/Components/Layout/MainLayout.razor b/MidrandBookshop/Components/Layout/MainLayout.razor index 747a64e..e874b22 100644 --- a/MidrandBookshop/Components/Layout/MainLayout.razor +++ b/MidrandBookshop/Components/Layout/MainLayout.razor @@ -1,4 +1,5 @@ -@inherits LayoutComponentBase +@using Blazored.Toast +@inherits LayoutComponentBase @inject NavigationManager Navigation
@@ -295,3 +296,4 @@
+ \ No newline at end of file diff --git a/MidrandBookshop/Setup.cs b/MidrandBookshop/Setup.cs index b1ea61c..5bd4940 100644 --- a/MidrandBookshop/Setup.cs +++ b/MidrandBookshop/Setup.cs @@ -15,6 +15,8 @@ public static class Setup services.AddRazorComponents() .AddInteractiveServerComponents(); + + services.AddBlazoredToast(); services.AddEndpointsApiExplorer(); diff --git a/MidrandBookshop/wwwroot/app.css b/MidrandBookshop/wwwroot/app.css index 6fc9a8d..5433325 100644 --- a/MidrandBookshop/wwwroot/app.css +++ b/MidrandBookshop/wwwroot/app.css @@ -112,3 +112,113 @@ h1:focus, h2:focus, h3:focus, h4:focus, p:focus, div:focus, span:focus { [tabindex="-1"]:focus { outline: none !important; } + +/* ========================================================================== + Global Toast Notification Framework Extensions + ========================================================================== */ + +.blazored-toast-container { + position: fixed; + /* 🛠️ Shift anchors from top-right to bottom-left */ + bottom: 24px; + left: 24px; + top: auto; + right: auto; + z-index: 2000 !important; + display: flex; + flex-direction: column-reverse; /* 💡 Newest toasts will now stack cleanly on top of old ones */ + gap: 12px; + max-width: 400px; + width: 100%; + pointer-events: none; +} + +.blazored-toast { + display: flex; + align-items: center; + padding: 16px 20px; + border-radius: var(--mb-radius); + background-color: var(--mb-card-bg); + color: var(--mb-text-dark); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.04); + border: 1px solid rgba(0, 0, 0, 0.05); + font-family: var(--font-ui); + font-size: 0.9rem; + font-weight: 500; + animation: toastFadeIn 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.125) forwards; +} + +/* Success Toast Core Variants */ +.blazored-toast-success { + border-left: 4px solid var(--mb-text-dark); +} + +/* Error Toast Core Variants */ +.blazored-toast-error { + border-left: 4px solid var(--mb-accent-red); + color: var(--mb-accent-red); +} + +.blazored-toast-icon { + display: inline-flex; + align-items: center; + justify-content: center; + margin-right: 12px; + flex-shrink: 0; +} + +/* Entry Transition Keyframes */ +@keyframes toastFadeIn { + from { + opacity: 0; + transform: translateY(-12px) scale(0.96); + } + + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.book-shadow { + filter: drop-shadow(5px 10px 15px rgba(0, 0, 0, 0.15)) drop-shadow(1px 2px 4px rgba(0, 0, 0, 0.1)); +} + +.sm-icon { + width: 14px; + height: 14px; + vertical-align: middle; +} + +/* 🛠️ Micro-interactions for the header icon placement */ +.btn-cart-icon:hover { + transform: scale(1.08); + background-color: var(--mb-text-dark) !important; +} + + .btn-cart-icon:hover svg { + stroke: #FFFFFF !important; + } + +@keyframes toastFadeIn { + from { + opacity: 0; + transform: translateX(-24px) scale(0.95); /* Slide rightward into view */ + } + + to { + opacity: 1; + transform: translateX(0) scale(1); + } +} + +.blazored-toast button.blazored-toast-close, +.blazored-toast-close-icon { + display: none !important; + visibility: hidden !important; + opacity: 0 !important; + width: 0 !important; + height: 0 !important; + padding: 0 !important; + margin: 0 !important; +} \ No newline at end of file From 554741c2e54f46fca87431c038b93958f2a8b64b Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Tue, 16 Jun 2026 14:49:03 +0200 Subject: [PATCH 2/2] Added user feedback toaster messages --- MidrandBookshop/Components/BookCard.razor.cs | 2 +- .../Components/Layout/MainLayout.razor.cs | 8 +++++--- .../Components/Pages/CartReview.razor.cs | 4 ++++ .../Components/Pages/Checkout.razor.cs | 19 ++++++++++++++++--- .../Components/Pages/PaymentSuccess.razor.cs | 1 + 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/MidrandBookshop/Components/BookCard.razor.cs b/MidrandBookshop/Components/BookCard.razor.cs index 239b108..587fff8 100644 --- a/MidrandBookshop/Components/BookCard.razor.cs +++ b/MidrandBookshop/Components/BookCard.razor.cs @@ -34,7 +34,7 @@ public partial class BookCard CartService.AddItem(productPriceFetch.Value, bookFetch.Value.Product!, authorFetch.Value); - ToastService.ShowSuccess($"Added '{Title}' to your order.", "Add To Cart"); + ToastService.ShowSuccess($"Added '{Title}' to your order.", "Cart Changed"); } catch { diff --git a/MidrandBookshop/Components/Layout/MainLayout.razor.cs b/MidrandBookshop/Components/Layout/MainLayout.razor.cs index a9c8944..830d77a 100644 --- a/MidrandBookshop/Components/Layout/MainLayout.razor.cs +++ b/MidrandBookshop/Components/Layout/MainLayout.razor.cs @@ -5,13 +5,13 @@ namespace MidrandBookshop.Components.Layout; public partial class MainLayout(CartService cartService) : IDisposable { - [Inject] - private AuthenticationStateProvider AuthStateProvider { get; set; } = default!; + [Inject] private AuthenticationStateProvider AuthStateProvider { get; set; } = default!; + [Inject] public IToastService ToastService { get; set; } = default!; private Cart ShoppingCart => cartService.ShoppingCart; private AuthenticationState? AuthState { get; set; } - private System.Security.Claims.ClaimsPrincipal? User { get; set; } + private ClaimsPrincipal? User { get; set; } private bool IsAuthenticated => User?.Identity?.IsAuthenticated ?? false; private string SearchInputBuffer { get; set; } = string.Empty; @@ -116,6 +116,8 @@ public partial class MainLayout(CartService cartService) : IDisposable cartService.RemoveOneItem(item.Price!.Id); await cartService.SaveCartToStorageAsync(); + + ToastService.ShowSuccess($"Removed {item.Product!.Name} from cart", "Cart Changed"); } private decimal GetCartTotal() => ShoppingCart?.TotalAmount ?? 0.00m; diff --git a/MidrandBookshop/Components/Pages/CartReview.razor.cs b/MidrandBookshop/Components/Pages/CartReview.razor.cs index 2745574..33afc4e 100644 --- a/MidrandBookshop/Components/Pages/CartReview.razor.cs +++ b/MidrandBookshop/Components/Pages/CartReview.razor.cs @@ -5,6 +5,8 @@ namespace MidrandBookshop.Components.Pages; public partial class CartReview(CartService cartService) { + [Inject] public IToastService ToastService { get; set; } = default!; + protected Cart ShoppingCart => cartService?.ShoppingCart!; protected async void IncreaseQty(CartItem item) @@ -32,5 +34,7 @@ public partial class CartReview(CartService cartService) cartService.RemoveOneItem(item.Price!.Id); await cartService.SaveCartToStorageAsync(); + + ToastService.ShowSuccess($"Removed {item.Product!.Name} from cart", "Cart Changed"); } } diff --git a/MidrandBookshop/Components/Pages/Checkout.razor.cs b/MidrandBookshop/Components/Pages/Checkout.razor.cs index d086e28..c82d88d 100644 --- a/MidrandBookshop/Components/Pages/Checkout.razor.cs +++ b/MidrandBookshop/Components/Pages/Checkout.razor.cs @@ -20,6 +20,7 @@ public partial class Checkout() [Inject] private AuthenticationStateProvider AuthStateProvider { get; set; } = default!; [Inject] public IJSRuntime JSRuntime { get; set; } = default!; [Inject] private CancellationToken CancellationToken { get; set; } = default!; + [Inject] public IToastService ToastService { get; set; } = default!; private Cart ShoppingCart => CartService.ShoppingCart; private ClaimsPrincipal? User { get; set; } @@ -63,7 +64,12 @@ public partial class Checkout() private async Task PayNow(MouseEventArgs args) { - if (IsProcessing) return; + if (IsProcessing) + { + ToastService.ShowWarning("Please wait, completing your payment", "Busy..."); + + return; + } try { @@ -111,7 +117,12 @@ public partial class Checkout() { var paymentFetch = await PaymentService.GetOrderPaymentAsync(orderId, CancellationToken); - if (paymentFetch.IsFailed) return; + if (paymentFetch.IsFailed) + { + ToastService.ShowError("Failed to get fetch your previously made payment", "Payment Check"); + + return; + } paymentId = paymentFetch.Value.Id; } @@ -150,8 +161,10 @@ public partial class Checkout() await JSRuntime.InvokeVoidAsync("eval", "document.getElementById('payfastForm').submit();"); } - catch + catch(Exception ex) { + ToastService.ShowError($"Failed to perform checkout: {ex.Message}", "Checkout"); + IsProcessing = false; StateHasChanged(); } diff --git a/MidrandBookshop/Components/Pages/PaymentSuccess.razor.cs b/MidrandBookshop/Components/Pages/PaymentSuccess.razor.cs index 51b567f..7f37c53 100644 --- a/MidrandBookshop/Components/Pages/PaymentSuccess.razor.cs +++ b/MidrandBookshop/Components/Pages/PaymentSuccess.razor.cs @@ -14,6 +14,7 @@ public partial class PaymentSuccess [Inject] private HashService HashService { get; set; } = default!; [Inject] private AuthenticationStateProvider AuthStateProvider { get; set; } = default!; [Inject] private CancellationToken CancellationToken { get; set; } = default!; + [Inject] public IToastService ToastService { get; set; } = default!; [Parameter] [SupplyParameterFromQuery(Name = "reference")]