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