@foreach (var book in PaginatedBooks)
{
NavigateToProduct(book.Id)">
- @book.Title
- by @book.Author
- @book.Category.ToUpper()
+ @book.Name
+ by @(ActiveAuthorFilterName ?? "Multiple Authors")
+ @ProductPrimaryCategoryCache[book.Id].ToUpper()
- @if (book.IsNew)
+ @if (book.Enabled)
{
NEW
}
-
R @book.Price.ToString("N0")
+
R @ProductPriceCache[book.Id].ToString("N0")
@@ -211,9 +197,6 @@
-
-
-
-
-
+ onclick="window.scrollTo({top: 0, behavior: 'smooth'}); return false;">
+
+
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/Home.razor.cs b/MidrandBookshop/Components/Pages/Home.razor.cs
index 780545c..5372ac5 100644
--- a/MidrandBookshop/Components/Pages/Home.razor.cs
+++ b/MidrandBookshop/Components/Pages/Home.razor.cs
@@ -1,15 +1,25 @@
-using LiteCharms.Features.MidrandBooks.AuthorBooks;
-using MidrandBookshop.Models;
+using LiteCharms.Features.MidrandBooks;
+using LiteCharms.Features.MidrandBooks.AuthorBooks;
+using LiteCharms.Features.MidrandBooks.Authors;
+using LiteCharms.Features.MidrandBooks.Categories;
+using LiteCharms.Features.MidrandBooks.Products;
+using LiteCharms.Features.MidrandBooks.Products.Models;
+using LiteCharms.Features.Models;
namespace MidrandBookshop.Components.Pages;
-public partial class Home(BooksService booksService)
+public partial class Home : ComponentBase
{
- [CascadingParameter]
- public string SharedSearchQuery { get; set; } = string.Empty;
+ [Inject] private ProductService ProductService { get; set; } = default!;
+ [Inject] private BooksService BooksService { get; set; } = default!;
+ [Inject] private AuthorService AuthorService { get; set; } = default!;
+ [Inject] private CategoryService CategoryService { get; set; } = default!;
+ [Inject] private NavigationManager Navigation { get; set; } = default!;
+
+ [CascadingParameter] public string SharedSearchQuery { get; set; } = string.Empty;
+ [SupplyParameterFromQuery] public long? AuthorId { get; set; }
public enum ViewMode { Grid, List }
-
private ViewMode CurrentViewMode = ViewMode.Grid;
private string ActiveCategory = "All";
private bool ShowExpandedCategories = false;
@@ -17,88 +27,136 @@ public partial class Home(BooksService booksService)
private string SelectedSortOption = "default";
private string ActivePriceFilter = "all";
private bool OnlyShowNew = false;
- private List
MainCategories = ["All", "Graphic Design", "Product Design", "Architecture"];
- private List DynamicExtendedCategories = [];
+
+ private List MainCategories { get; set; } = ["All"];
+ private List DynamicExtendedCategories { get; set; } = [];
+
private int ItemsPerPage = 12;
private int VisibleCount = 12;
- private List BooksCollection = [];
- private IEnumerable FilteredData
+
+ private List ProductsCollection { get; set; } = [];
+
+ protected string? ActiveAuthorFilterName { get; private set; }
+
+ private Dictionary ProductPriceCache { get; set; } = [];
+ private Dictionary ProductPrimaryCategoryCache { get; set; } = [];
+
+ private IEnumerable FilteredData
{
get
{
- var data = BooksCollection.AsEnumerable();
+ var data = ProductsCollection.AsEnumerable();
+ // Category filtering restricts rendering solely when checking the open catalog
+ if (ActiveCategory != "All" && !AuthorId.HasValue)
+ data = data.Where(p => ProductPrimaryCategoryCache.ContainsKey(p.Id) &&
+ ProductPrimaryCategoryCache[p.Id] == ActiveCategory);
+
+ // Text matching is completely restricted from evaluating author metadata properties
if (!string.IsNullOrWhiteSpace(SharedSearchQuery))
{
var q = SharedSearchQuery.Trim();
- data = data.Where(b =>
- b.Title.Contains(q, StringComparison.OrdinalIgnoreCase) ||
- b.Author.Contains(q, StringComparison.OrdinalIgnoreCase) ||
- b.Isbn.Contains(q, StringComparison.OrdinalIgnoreCase)
- );
+
+ data = data.Where(p => (p.Name ?? "").Contains(q, StringComparison.OrdinalIgnoreCase));
}
- if (ActiveCategory != "All")
- {
- data = data.Where(b => b.Category.Equals(ActiveCategory, StringComparison.OrdinalIgnoreCase));
- }
-
- if (OnlyShowNew) { data = data.Where(b => b.IsNew); }
-
- data = ActivePriceFilter switch
- {
- "under-500" => data.Where(b => b.Price < 500),
- "500-1000" => data.Where(b => b.Price >= 500 && b.Price <= 1000),
- "over-1000" => data.Where(b => b.Price > 1000),
- _ => data
- };
-
return data;
}
}
- private IEnumerable SortedAndFilteredBooks => SelectedSortOption switch
+
+ private IEnumerable PaginatedBooks => FilteredData.Take(VisibleCount);
+
+ private bool HasMoreItems => FilteredData.Count() > VisibleCount;
+
+ protected override async Task OnParametersSetAsync() => await LoadCatalogDataAsync();
+
+ private async Task LoadCatalogDataAsync()
{
- "price-low" => FilteredData.OrderBy(b => b.Price),
- "price-high" => FilteredData.OrderByDescending(b => b.Price),
- "title-asc" => FilteredData.OrderBy(b => b.Title),
- _ => FilteredData
- };
- private IEnumerable PaginatedBooks => SortedAndFilteredBooks.Take(VisibleCount);
- private int TotalFilteredCount => FilteredData.Count();
- private bool HasMoreItems => VisibleCount < TotalFilteredCount;
+ ProductsCollection.Clear();
+ ProductPriceCache.Clear();
+ ProductPrimaryCategoryCache.Clear();
+ ActiveAuthorFilterName = null;
- protected override void OnInitialized()
- {
- var extraSourceCategories = new[] { "Fine Arts", "Science", "Photography", "Typography", "Interior Design", "Industrialism", "Fashion", "Curation Studies" };
- DynamicExtendedCategories.AddRange(extraSourceCategories);
-
- // Updated mock items to supply long IDs matching your screenshot items
- BooksCollection.Add(new BookItem { Id = 1L, Title = "Letters from M/M (Paris)", Author = "M/M Paris", Price = 720, Category = "Graphic Design", IsNew = true, Isbn = "9782915173" });
- BooksCollection.Add(new BookItem { Id = 2L, Title = "Daan Paans: Floating Signifiers", Author = "Daan Paans", Price = 540, Category = "Product Design", IsNew = true, Isbn = "9789492051" });
- BooksCollection.Add(new BookItem { Id = 3L, Title = "Album Architectures, Maputo", Author = "Guedes Archive", Price = 350, Category = "Architecture", IsNew = true, Isbn = "9780620751" });
-
- var designPrefixes = new[] { "Minimalist", "Monolithic", "Architectural", "Japanese", "Scandinavian" };
- var designNouns = new[] { "Structures", "Typologies", "Forms & Spaces", "Systems Matrix", "Graphic Ephemera" };
- var designers = new[] { "J. Morrison", "K. Fujita", "Studio Bouroullec", "Es Devlin", "Kenya Hara" };
-
- var entireCategoryPool = MainCategories.Concat(DynamicExtendedCategories).Where(c => c != "All").ToArray();
- var random = new Random(42);
-
- for (int i = 4; i <= 60; i++)
+ // Pipeline A: Extract scoped books directly associated with an ID token parameter
+ if (AuthorId.HasValue)
{
- BooksCollection.Add(new BookItem
+ var authorResult = await AuthorService.GetAuthorAsync(AuthorId.Value);
+
+ if (authorResult.IsSuccess && authorResult.Value != null)
{
- Id = (long)i,
- Title = $"{designPrefixes[random.Next(designPrefixes.Length)]} {designNouns[random.Next(designNouns.Length)]} (Vol. {random.Next(1, 4)})",
- Author = designers[random.Next(designers.Length)],
- Price = random.Next(25, 135) * 10,
- Category = entireCategoryPool[random.Next(entireCategoryPool.Length)],
- IsNew = random.NextDouble() > 0.7,
- Isbn = $"978000000{i}"
- });
+ var author = authorResult.Value;
+
+ ActiveAuthorFilterName = author.PublisherType == PublisherTypes.Company && !string.IsNullOrWhiteSpace(author.Company)
+ ? author.Company
+ : $"{author.Name} {author.LastName}".Trim();
+ }
+
+ var authorBooksResult = await BooksService.GetBooksByAuthorAsync(AuthorId.Value);
+
+ if (authorBooksResult.IsSuccess && authorBooksResult.Value != null)
+ {
+ foreach (var authorBook in authorBooksResult.Value)
+ {
+ if (authorBook.Product != null)
+ {
+ var product = authorBook.Product;
+
+ ProductsCollection.Add(product);
+
+ ProductPriceCache[product.Id] = product.Price?.Amount ?? 0m;
+
+ var categoryResult = await ProductService.GetProductCategoriesAsync(product.Id);
+
+ ProductPrimaryCategoryCache[product.Id] = (categoryResult.IsSuccess && categoryResult.Value.Length > 0)
+ ? (categoryResult.Value[0].Name ?? "General")
+ : "General";
+ }
+ }
+ }
+ return;
+ }
+
+ // Pipeline B: Safe structural fallback mapping utilizing the exact backend DateRange object setup
+ var selectionRange = new DateRange
+ {
+ From = new DateOnly(2020, 1, 1),
+ To = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(1)),
+ MaxRecords = 100
+ };
+
+ var allProductsResult = await ProductService.GetProductsAsync(0, selectionRange);
+
+ if (allProductsResult.IsSuccess && allProductsResult.Value != null)
+ {
+ ProductsCollection = allProductsResult.Value.ToList();
+
+ foreach (var product in ProductsCollection)
+ {
+ var priceResult = await ProductService.GetProductPriceAsync(product.Id);
+ ProductPriceCache[product.Id] = priceResult.IsSuccess ? priceResult.Value.Amount : 0m;
+
+ var categoryResult = await ProductService.GetProductCategoriesAsync(product.Id);
+ ProductPrimaryCategoryCache[product.Id] = (categoryResult.IsSuccess && categoryResult.Value.Length > 0)
+ ? (categoryResult.Value[0].Name ?? "General")
+ : "General";
+ }
+ }
+
+ var categoriesResult = await CategoryService.GetCategoriesAsync();
+
+ if (categoriesResult.IsSuccess && categoriesResult.Value != null)
+ {
+ var cleanNames = categoriesResult.Value
+ .Select(c => c.Name)
+ .Where(n => !string.IsNullOrEmpty(n)).Cast()
+ .ToList();
+
+ MainCategories = ["All", .. cleanNames.Take(3)];
+ DynamicExtendedCategories = [.. cleanNames.Skip(3)];
}
}
+ private void ClearAuthorFilter() => Navigation.NavigateTo("/");
private void NavigateToProduct(long id) => Navigation.NavigateTo($"/product/{id}");
private void SetViewMode(ViewMode targetMode) => CurrentViewMode = targetMode;
private void SelectCategory(string categoryName) { ActiveCategory = categoryName; VisibleCount = ItemsPerPage; }
@@ -109,4 +167,4 @@ public partial class Home(BooksService booksService)
private void ToggleNewArrivalsOnly(ChangeEventArgs e) { OnlyShowNew = e.Value is bool b && b; VisibleCount = ItemsPerPage; }
private void ResetFilters() { SelectedSortOption = "default"; ActivePriceFilter = "all"; OnlyShowNew = false; VisibleCount = ItemsPerPage; }
private void LoadNextPage() { if (HasMoreItems) VisibleCount += ItemsPerPage; }
-}
+}
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/Product.razor b/MidrandBookshop/Components/Pages/Product.razor
deleted file mode 100644
index aca17d0..0000000
--- a/MidrandBookshop/Components/Pages/Product.razor
+++ /dev/null
@@ -1,137 +0,0 @@
-@page "/product/{BookId:long}"
-@inject NavigationManager Navigation
-
-
-
- Navigation.NavigateTo("/")' class="crumb-link">Books
- /
- @BookTitle
-
-
-
-
-
-
-
-
- @if (IsPhysicalBook)
- {
- Book
- }
- @if (IsEBook)
- {
- E-Book
- }
- @if (CanReadOnline)
- {
- Read Online
- }
-
-
-
-
- @foreach (var img in Thumbnails)
- {
-
ActiveImageUrl = img">
-
-
- }
-
-
-
-
-
-
-
@BookTitle
-
R @Price.ToString("N2")
-
-
-
- -
- @Quantity
- +
-
-
- Add to Cart
-
-
-
-
-
-
-
Description
-
@BookDescription
-
-
-
-
About the Author
-
@AuthorBio
-
- View all books by @AuthorName →
-
-
-
-
-
-
-@code {
- [Parameter] public long BookId { get; set; }
-
- // Mock State - In production, pull these via a Service using BookId inside OnInitialized
- private string BookTitle { get; set; } = "Letters from M/M (Paris)";
- private string AuthorName { get; set; } = "M/M Paris";
- private string AuthorBio { get; set; } = "M/M Paris is an art and design partnership consisting of Michaël Amzalag and Mathias Augustyniak, established in 1992. Renowned globally for their influence on fashion, music, and contemporary art layout structures.";
- private string BookDescription { get; set; } = "An exquisite archive tracking visual graphics, typography, and structural design curation over three decades. Beautifully bound with matte-coated plates and custom layouts.";
- private decimal Price { get; set; } = 720.00m;
-
- // Dynamic Ratings State
- private double CurrentRating { get; set; } = 4.7;
-
- // Formats supported
- private bool IsPhysicalBook { get; set; } = true;
- private bool IsEBook { get; set; } = true;
- private bool CanReadOnline { get; set; } = false;
-
- // Image Caching Gallery State
- private string ActiveImageUrl { get; set; } = "images/book-cover-large.png";
- private List Thumbnails { get; set; } = new()
- {
- "images/book-cover-large.png",
- "images/book-inside-1.png",
- "images/book-inside-2.png"
- };
-
- private int Quantity { get; set; } = 1;
-
- protected override void OnInitialized()
- {
- // Default the gallery viewer context logic
- if (Thumbnails.Any())
- {
- ActiveImageUrl = Thumbnails.First();
- }
- }
-
- private void IncreaseQty() => Quantity++;
- private void DecreaseQty() { if (Quantity > 1) Quantity--; }
-
- private void HandleAddToCart()
- {
- // Event logic hooked into your structural state layout
- }
-
- private void ViewAllAuthorBooks()
- {
- Navigation.NavigateTo($"/catalog?author={Uri.EscapeDataString(AuthorName)}");
- }
-}
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/ProductView.razor b/MidrandBookshop/Components/Pages/ProductView.razor
new file mode 100644
index 0000000..e22aafe
--- /dev/null
+++ b/MidrandBookshop/Components/Pages/ProductView.razor
@@ -0,0 +1,126 @@
+@page "/product/{BookId:long}"
+@rendermode InteractiveServer
+
+
+ @if (IsLoading)
+ {
+
+
+ Loading product details...
+
+
Loading curated details...
+
+ }
+ else if (CurrentProduct == null)
+ {
+
+
+
+
+
+
+
Artifact Not Found
+
The requested book could not be found in our current catalog.
+
Navigation.NavigateTo("/")'>Return to Books
+
+ }
+ else
+ {
+
+ Navigation.NavigateTo("/")' class="crumb-link">Books
+ /
+ @CurrentProduct.Name
+
+
+
+
+
+ @if (!string.IsNullOrWhiteSpace(ActiveImageUrl))
+ {
+
+ }
+ else
+ {
+
+
+ @PrimaryCategory.ToUpper()
+ CURATED EDITION
+
+
+ }
+
+
+ @if (CurrentProduct.Enabled)
+ {
+ Physical Book
+ }
+ In Stock
+
+
+
+ @if (Thumbnails.Count > 1)
+ {
+
+ @foreach (var img in Thumbnails)
+ {
+ var currentImg = img;
+
ActiveImageUrl = currentImg">
+
+
+ }
+
+ }
+
+
+
+
+
+
@CurrentProduct.Name
+
R @LivePrice.ToString("N2")
+
+
+
+ -
+ @Quantity
+ +
+
+
+ Add to Cart
+
+
+
+
+
+ @if (!string.IsNullOrWhiteSpace(CurrentProduct.Description))
+ {
+
+
Description
+
@CurrentProduct.Description
+
+ }
+
+
+
About the Author
+
+ @(string.IsNullOrWhiteSpace(CurrentProduct.Metadata?.Manufacturer)
+ ? "Details regarding this publisher/author are maintained inside our curated archives."
+ : $"{CurrentProduct.Metadata.Manufacturer} is globally recognized for their direct contribution to this specific genre spectrum.")
+
+
+ View all books by @AuthorName →
+
+
+
+
+ }
+
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/ProductView.razor.cs b/MidrandBookshop/Components/Pages/ProductView.razor.cs
new file mode 100644
index 0000000..99e591e
--- /dev/null
+++ b/MidrandBookshop/Components/Pages/ProductView.razor.cs
@@ -0,0 +1,104 @@
+using LiteCharms.Features.MidrandBooks;
+using LiteCharms.Features.MidrandBooks.Authors;
+using LiteCharms.Features.MidrandBooks.Authors.Models;
+using LiteCharms.Features.MidrandBooks.Products;
+using LiteCharms.Features.MidrandBooks.Products.Models;
+
+namespace MidrandBookshop.Components.Pages;
+
+public partial class ProductView : ComponentBase
+{
+ [Parameter] public long BookId { get; set; }
+
+ [Inject] private ProductService ProductService { get; set; } = default!;
+ [Inject] private AuthorService AuthorService { get; set; } = default!;
+ [Inject] private NavigationManager Navigation { get; set; } = default!;
+
+ protected bool IsLoading { get; private set; } = true;
+ protected Product? CurrentProduct { get; private set; }
+ protected decimal LivePrice { get; private set; } = 0.00m;
+ protected string AuthorName { get; private set; } = "Unknown Author";
+ protected string PrimaryCategory { get; private set; } = "General";
+
+ protected string ActiveImageUrl { get; set; } = string.Empty;
+ protected List Thumbnails { get; private set; } = [];
+ protected int Quantity { get; private set; } = 1;
+
+ protected Author? CurrentAuthor { get; private set; }
+
+ protected override async Task OnParametersSetAsync()
+ {
+ try
+ {
+ IsLoading = true;
+ CurrentProduct = null;
+ CurrentAuthor = null;
+ Thumbnails.Clear();
+
+ // 1. Resolve product listing details
+ var productResult = await ProductService.GetProductAsync(BookId);
+
+ if (productResult.IsSuccess && productResult.Value != null)
+ {
+ CurrentProduct = productResult.Value;
+ AuthorName = CurrentProduct.Metadata?.Manufacturer ?? "Unknown Author";
+
+ // 2. Load pricing data
+ var priceResult = await ProductService.GetProductPriceAsync(BookId);
+ LivePrice = priceResult.IsSuccess ? priceResult.Value.Amount : 0m;
+
+ // 3. Extract active catalog categories
+ var categoryResult = await ProductService.GetProductCategoriesAsync(BookId);
+ if (categoryResult.IsSuccess && categoryResult.Value.Length > 0)
+ {
+ PrimaryCategory = categoryResult.Value[0].Name ?? "General";
+ }
+
+ // 4. Retrieve complete contextual model through the newly instantiated AuthorService lookup
+ var authorResult = await AuthorService.GetAuthorByProductIdAsync(BookId);
+ if (authorResult.IsSuccess && authorResult.Value != null)
+ {
+ CurrentAuthor = authorResult.Value;
+
+ // Format fully qualified author text cleanly depending on their publisher model details
+ if (CurrentAuthor.PublisherType == PublisherTypes.Company && !string.IsNullOrWhiteSpace(CurrentAuthor.Company))
+ AuthorName = CurrentAuthor.Company;
+ else
+ AuthorName = $"{CurrentAuthor.Name} {CurrentAuthor.LastName}".Trim();
+ }
+
+ // 5. Build presentation image viewer variables
+ if (!string.IsNullOrWhiteSpace(CurrentProduct.ImageUrl))
+ Thumbnails.Add(CurrentProduct.ImageUrl);
+
+ if (CurrentProduct.ThumbnailUrls != null && CurrentProduct.ThumbnailUrls?.Length != 0)
+ foreach (var url in CurrentProduct.ThumbnailUrls!)
+ if (!string.IsNullOrWhiteSpace(url) && !Thumbnails.Contains(url)) Thumbnails.Add(url);
+
+ ActiveImageUrl = Thumbnails.FirstOrDefault() ?? string.Empty;
+ }
+ }
+ catch (Exception)
+ {
+ CurrentProduct = null;
+ }
+ finally
+ {
+ IsLoading = false;
+ }
+ }
+
+ protected void IncreaseQty() => Quantity++;
+ protected void DecreaseQty() { if (Quantity > 1) Quantity--; }
+
+ protected void HandleAddToCart()
+ {
+ if (CurrentProduct == null) return;
+ }
+
+ protected void ViewAllAuthorBooks()
+ {
+ if (CurrentAuthor is not null)
+ Navigation.NavigateTo($"/?authorId={CurrentAuthor.Id}");
+ }
+}
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/Product.razor.css b/MidrandBookshop/Components/Pages/ProductView.razor.css
similarity index 62%
rename from MidrandBookshop/Components/Pages/Product.razor.css
rename to MidrandBookshop/Components/Pages/ProductView.razor.css
index 1e64af7..898af69 100644
--- a/MidrandBookshop/Components/Pages/Product.razor.css
+++ b/MidrandBookshop/Components/Pages/ProductView.razor.css
@@ -1,20 +1,36 @@
-.product-container {
+/* ==========================================================================
+ Structural Layout Containers
+ ========================================================================== */
+
+.product-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1.5rem;
font-family: system-ui, -apple-system, sans-serif;
}
-/* Breadcrumbs */
+.product-layout {
+ display: grid;
+ grid-template-columns: 1.1fr 0.9fr;
+ gap: 4rem;
+}
+
+/* ==========================================================================
+ Breadcrumb Navigation Links
+ ========================================================================== */
+
.breadcrumb {
font-size: 0.85rem;
margin-bottom: 2.5rem;
color: #8c8c8c;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
}
.crumb-link {
cursor: pointer;
- transition: color 0.2s;
+ transition: color 0.2s ease;
}
.crumb-link:hover {
@@ -30,52 +46,46 @@
font-weight: 500;
}
-/* Two-Column Grid Layout Structure */
-.product-layout {
- display: grid;
- grid-template-columns: 1.1fr 0.9fr;
- gap: 4rem;
-}
+/* ==========================================================================
+ Left Section: Media Gallery Components
+ ========================================================================== */
-@media (max-width: 992px) {
- .product-layout {
- grid-template-columns: 1fr;
- gap: 2.5rem;
- }
-}
-
-/* Left Section: Visual Images/Thumbnails Layout */
.gallery-section {
display: flex;
flex-direction: column;
gap: 1rem;
+ width: 100%;
}
.main-image-wrapper {
position: relative;
background-color: #f9f9f9;
border-radius: 8px;
- padding: 3rem;
+ padding: 3rem 3rem 4.5rem 3rem;
display: flex;
justify-content: center;
align-items: center;
min-height: 480px;
+ width: 100%;
}
.main-image {
max-height: 450px;
+ max-width: 100%;
object-fit: contain;
mix-blend-mode: multiply;
}
-/* Format Badges Overlay Styles */
+/* Dynamic Overlaid Attribute Badges */
.format-badges {
position: absolute;
- top: 1rem;
- left: 1rem;
+ bottom: 1.5rem;
+ left: 50%;
+ transform: translateX(-50%);
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
+ white-space: nowrap;
}
.badge {
@@ -105,9 +115,10 @@
border-color: #bbf7d0;
}
-/* Thumbnails container */
+/* Interactive Gallery Thumbnails Grid */
.thumbnail-grid {
display: flex;
+ flex-wrap: wrap;
gap: 1rem;
}
@@ -122,7 +133,8 @@
transition: all 0.2s ease;
}
- .thumbnail-wrapper:hover, .thumbnail-wrapper.active {
+ .thumbnail-wrapper:hover,
+ .thumbnail-wrapper.active {
border-color: #111;
}
@@ -133,7 +145,10 @@
mix-blend-mode: multiply;
}
-/* Right Section: Core Text Typography Controls */
+/* ==========================================================================
+ Right Section: Product Details & Typography Controls
+ ========================================================================== */
+
.details-section {
display: flex;
flex-direction: column;
@@ -144,6 +159,8 @@
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
+ flex-wrap: wrap;
+ gap: 0.5rem;
}
.author-name {
@@ -151,7 +168,7 @@
color: #666;
}
-/* Dynamic Stars C# rendering mapping */
+/* Review Star Components */
.rating-stars {
display: flex;
align-items: center;
@@ -164,7 +181,7 @@
}
.star.filled {
- color: #111; /* Solid black stars fits your clean aesthetic perfectly */
+ color: #ffc107;
}
.rating-text {
@@ -175,7 +192,7 @@
.product-title {
font-size: 2.5rem;
- font-family: 'Playfair Display', serif, Georgia; /* Fits the luxury typography tone */
+ font-family: 'Playfair Display', serif, Georgia;
font-weight: 400;
line-height: 1.2;
margin-bottom: 1rem;
@@ -189,7 +206,10 @@
color: #111;
}
-/* Standard E-commerce Action Bar Block */
+/* ==========================================================================
+ Interactive E-Commerce Selection Bars
+ ========================================================================== */
+
.purchase-actions {
display: flex;
gap: 1rem;
@@ -215,7 +235,7 @@
align-items: center;
justify-content: center;
border-radius: 50%;
- transition: background-color 0.2s;
+ transition: background-color 0.2s ease;
}
.qty-btn:hover {
@@ -237,7 +257,8 @@
font-weight: 500;
letter-spacing: 0.02em;
cursor: pointer;
- transition: background-color 0.2s;
+ transition: background-color 0.2s ease;
+ padding: 0.5rem 1.5rem;
}
.btn-add-to-cart:hover {
@@ -250,7 +271,10 @@
margin-bottom: 2rem;
}
-/* General Layout Text and Cross-Selling */
+/* ==========================================================================
+ Informational Text Elements & Links
+ ========================================================================== */
+
.info-block {
margin-bottom: 2rem;
}
@@ -263,7 +287,8 @@
margin-bottom: 0.75rem;
}
-.description-text, .author-bio {
+.description-text,
+.author-bio {
font-size: 0.95rem;
line-height: 1.6;
color: #444;
@@ -292,3 +317,44 @@
.btn-text-link:hover {
color: #444;
}
+
+/* ==========================================================================
+ Responsive Adaptations & Breakpoints
+ ========================================================================== */
+
+@media (max-width: 992px) {
+ .product-layout {
+ grid-template-columns: 1fr;
+ gap: 2.5rem;
+ }
+
+ .main-image-wrapper {
+ min-height: 380px;
+ }
+}
+
+@media (max-width: 576px) {
+ .product-container {
+ padding: 1rem 1rem;
+ }
+
+ .product-title {
+ font-size: 1.85rem;
+ }
+
+ .purchase-actions {
+ flex-direction: column;
+ gap: 0.75rem;
+ }
+
+ .quantity-picker {
+ justify-content: space-between;
+ width: 100%;
+ padding: 0.5rem;
+ }
+
+ .btn-add-to-cart {
+ width: 100%;
+ padding: 0.85rem;
+ }
+}
diff --git a/MidrandBookshop/MidrandBookshop.csproj b/MidrandBookshop/MidrandBookshop.csproj
index 8705a98..fb22307 100644
--- a/MidrandBookshop/MidrandBookshop.csproj
+++ b/MidrandBookshop/MidrandBookshop.csproj
@@ -18,13 +18,13 @@
-
+
-
+
diff --git a/MidrandBookshop/Models/BookItem.cs b/MidrandBookshop/Models/BookItem.cs
deleted file mode 100644
index c2f93da..0000000
--- a/MidrandBookshop/Models/BookItem.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace MidrandBookshop.Models;
-
-public class BookItem
-{
- public long Id { get; set; } // Refactored to hold unique record indices of type long
- public string Title { get; set; } = string.Empty;
- public string Author { get; set; } = string.Empty;
- public decimal Price { get; set; }
- public string Category { get; set; } = string.Empty;
- public bool IsNew { get; set; }
- public string Isbn { get; set; } = string.Empty;
-}