293 lines
18 KiB
Plaintext
293 lines
18 KiB
Plaintext
@inherits LayoutComponentBase
|
|
@inject NavigationManager Navigation
|
|
|
|
<div class="position-relative vh-100 d-flex flex-column justify-content-between overflow-hidden" style="background-color: #F9F9F9;">
|
|
|
|
@* --- CART SYSTEM SIDE PANEL BACKDROP LAYER --- *@
|
|
<div class="cart-overlay @(IsCartOpen ? "is-visible" : "")" @onclick="ToggleCart"></div>
|
|
<div class="cart-drawer @(IsCartOpen ? "is-open" : "") d-flex flex-column bg-white shadow-lg">
|
|
<div class="cart-header d-flex align-items-center justify-content-between p-4 border-bottom">
|
|
<h5 class="fw-bold m-0 text-dark tracking-tight" style="font-family: 'Inter', sans-serif; font-size: 1.1rem;">
|
|
YOUR CART (@CartItems.Sum(i => i.Quantity))
|
|
</h5>
|
|
<button class="btn btn-sm text-dark p-1 border-0" @onclick="ToggleCart" type="button">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="cart-body flex-grow-1 overflow-y-auto p-4">
|
|
@if (!CartItems.Any())
|
|
{
|
|
<div class="h-100 d-flex flex-column align-items-center justify-content-center text-muted py-5">
|
|
<svg class="mb-3 opacity-50" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z M3 6h18 M16 10a4 4 0 0 1-8 0" />
|
|
</svg>
|
|
<span class="small tracking-wide">Your collection is empty.</span>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="d-flex flex-column gap-4">
|
|
@foreach (var item in CartItems)
|
|
{
|
|
<div class="cart-item d-flex gap-3 align-items-start pb-3 border-bottom-dashed">
|
|
<div class="cart-item-thumb bg-dark text-white-50 d-flex align-items-center justify-content-center px-2 text-center" style="width: 54px; height: 74px; font-size: 0.45rem; letter-spacing: 0.5px;">
|
|
[ COVER ]
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h6 class="text-dark small fw-bold mb-0 text-truncate" style="max-width: 180px;">@item.Title</h6>
|
|
<p class="text-muted xx-small mb-2">by @item.Author</p>
|
|
<div class="d-flex align-items-center justify-content-between">
|
|
<div class="quantity-picker d-flex align-items-center border rounded-pill bg-light">
|
|
<button class="btn btn-sm py-0 px-2 text-dark border-0" @onclick="() => ChangeQuantity(item, -1)" type="button">-</button>
|
|
<span class="px-1 text-dark fw-medium" style="font-size: 0.75rem;">@item.Quantity</span>
|
|
<button class="btn btn-sm py-0 px-2 text-dark border-0" @onclick="() => ChangeQuantity(item, 1)" type="button">+</button>
|
|
</div>
|
|
<span class="small fw-semibold text-dark">R @(item.Price * item.Quantity)</span>
|
|
</div>
|
|
</div>
|
|
<button class="btn text-muted p-0 border-0 mt-1 align-self-start" style="background: none;" @onclick="() => RemoveFromCart(item)" type="button">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
|
</button>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@if (CartItems.Any())
|
|
{
|
|
<div class="cart-footer p-4 bg-light border-top mt-auto">
|
|
<div class="d-flex align-items-center justify-content-between mb-4">
|
|
<span class="text-secondary small fw-medium tracking-wider">TOTAL DUE</span>
|
|
<span class="text-dark h5 fw-bold mb-0">R @GetCartTotal()</span>
|
|
</div>
|
|
<div class="d-flex flex-column gap-2">
|
|
<button class="btn btn-dark w-100 rounded-pill py-2.5 fw-medium shadow-sm small tracking-wide" @onclick="RedirectToCheckout" type="button">
|
|
Proceed to Checkout
|
|
</button>
|
|
<button class="btn btn-outline-dark w-100 rounded-pill py-2.5 fw-medium bg-white small tracking-wide" @onclick="RedirectToCart" type="button">
|
|
Go to Cart Page
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@* --- TOP FIXED LAYOUT AREA --- *@
|
|
<div class="w-100 position-relative flex-shrink-0" style="z-index: 1020;">
|
|
@* Decorative Background SVG Watermark Line Graphic *@
|
|
<div class="position-absolute top-0 start-0 overflow-hidden d-none d-md-block" style="z-index: -1; pointer-events: none; opacity: 0.08; transform: translate(-10%, -10%);">
|
|
<svg width="480" height="480" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<circle cx="100" cy="100" r="98" stroke="#1A1A1A" stroke-width="0.25" stroke-dasharray="0.5 1.5" />
|
|
<circle cx="100" cy="100" r="92" stroke="#1A1A1A" stroke-width="0.4" />
|
|
<circle cx="100" cy="100" r="86" stroke="#1A1A1A" stroke-width="0.2" stroke-dasharray="2 1" />
|
|
<circle cx="100" cy="100" r="80" stroke="#1A1A1A" stroke-width="0.3" />
|
|
<path d="M100 20 A80 80 0 0 1 180 100 A80 80 0 0 1 100 180 A80 80 0 0 1 20 100 A80 80 0 0 1 100 20" stroke="#1A1A1A" stroke-width="0.15" />
|
|
<path d="M100 20 A80 80 0 0 0 180 100 A80 80 0 0 0 100 180 A80 80 0 0 0 20 100 A80 80 0 0 0 100 20" stroke="#1A1A1A" stroke-width="0.15" />
|
|
<line x1="40" y1="100" x2="160" y2="100" stroke="#1A1A1A" stroke-width="0.2" stroke-dasharray="1 4" />
|
|
<line x1="100" y1="40" x2="100" y2="160" stroke="#1A1A1A" stroke-width="0.2" stroke-dasharray="1 4" />
|
|
<g transform="translate(65, 82)" stroke="#1A1A1A" stroke-width="0.6" stroke-linecap="round" stroke-linejoin="round" fill="none">
|
|
<path d="M16 18 C 16 10, 24 10, 24 18" stroke-width="0.5" />
|
|
<line x1="20" y1="10" x2="20" y2="4" />
|
|
<path d="M28 18 C 28 14, 34 14, 34 18" stroke-width="0.5" />
|
|
<line x1="31" y1="14" x2="31" y2="7" />
|
|
<rect x="3" y="8" width="5" height="10" stroke-width="0.5" />
|
|
<rect x="11" y="5" width="5" height="13" stroke-width="0.5" />
|
|
<rect x="38" y="3" width="7" height="15" stroke-width="0.5" />
|
|
<polygon points="38,3 41.5,0 45,3" stroke-width="0.4" />
|
|
<rect x="49" y="9" width="6" height="9" stroke-width="0.5" />
|
|
<line x1="0" y1="18" x2="70" y2="18" stroke-width="0.75" />
|
|
</g>
|
|
</svg>
|
|
</div>
|
|
|
|
<nav class="navbar navbar-expand-lg py-3" style="pointer-events: auto;">
|
|
<div class="container-fluid px-md-5">
|
|
<a class="navbar-brand d-flex align-items-center" href="/" style="transform: scale(1.6); transform-origin: left center; margin-right: 4rem;">
|
|
<svg class="me-2" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 17c-2.5 0-4.5-1.8-4.5-4s2-4 4.5-4 4.5 1.8 4.5 4-2 4-4.5 4Z" />
|
|
<path d="M12 9c2.2 0 4-1.6 4-3.5S14.2 2 12 2 8 3.6 8 5.5 9.8 9 12 9Z" />
|
|
<path d="M2 12h20" stroke-width="1" stroke-dasharray="1 2" opacity="0.5" />
|
|
</svg>
|
|
<span class="fw-bold tracking-tight text-dark" style="font-size: 0.65rem; letter-spacing: 0.8px; font-family: 'Inter', sans-serif;">MIDRAND BOOKS</span>
|
|
</a>
|
|
|
|
<div class="collapse navbar-collapse justify-content-center" id="navbarNav">
|
|
<div class="nav-pill-wrapper d-flex align-items-center">
|
|
<a class="nav-pill-link active" href="/">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" /></svg>
|
|
Books
|
|
</a>
|
|
<a class="nav-pill-link" href="/audiobooks">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 18v-6a9 9 0 0 1 18 0v6" /><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z" /></svg>
|
|
Audiobooks
|
|
</a>
|
|
<a class="nav-pill-link" href="/ebooks">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2" ry="2" /><line x1="12" y1="18" x2="12.01" y2="18" /></svg>
|
|
E-Books
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex align-items-center gap-2 position-relative">
|
|
<div class="search-input-container d-flex align-items-center @(IsSearchActive ? "is-active" : "")">
|
|
<input type="text"
|
|
class="form-control form-control-sm rounded-pill border-light-subtle px-3 text-dark bg-light custom-search-field"
|
|
placeholder="Search by ISBN, Author, Title..."
|
|
value="@GlobalSearchQuery"
|
|
@oninput="OnSearchInput" />
|
|
</div>
|
|
|
|
<button class="btn btn-sm rounded-circle d-flex align-items-center justify-content-center border-0 p-0 transition-smooth @(IsSearchActive ? "bg-dark text-white" : "bg-light text-dark")"
|
|
style="width: 32px; height: 32px;"
|
|
@onclick="ToggleGlobalSearch"
|
|
type="button">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
<circle cx="11" cy="11" r="8"></circle>
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
</svg>
|
|
</button>
|
|
|
|
<button class="btn d-flex align-items-center justify-content-center p-2 position-relative border-0"
|
|
style="background: transparent; color: #1A1A1A; width: 36px; height: 36px;"
|
|
@onclick="ToggleCart"
|
|
type="button">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
|
|
<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z M3 6h18 M16 10a4 4 0 0 1-8 0" />
|
|
</svg>
|
|
@if (CartItems.Any())
|
|
{
|
|
<span class="cart-badge">@CartItems.Sum(i => i.Quantity)</span>
|
|
}
|
|
</button>
|
|
|
|
<a href="/profile" class="btn btn-dark rounded-pill px-3 py-1 d-inline-flex align-items-center gap-2 btn-sm fw-medium shadow-sm">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
|
<circle cx="12" cy="7" r="4" />
|
|
</svg>
|
|
Log In
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
|
|
@* --- MAIN INDEPENDENT SCROLL LAYER --- *@
|
|
<div class="w-100 flex-grow-1 overflow-y-auto d-flex flex-column justify-content-between">
|
|
<main class="position-relative" style="z-index: 5;">
|
|
<CascadingValue Value="GlobalSearchQuery">
|
|
@Body
|
|
</CascadingValue>
|
|
</main>
|
|
|
|
<footer class="custom-site-footer border-top mt-auto">
|
|
<div class="container-fluid px-md-5">
|
|
<div class="row g-4 pb-4 justify-content-between footer-content-section">
|
|
<div class="col-12 col-md-5">
|
|
<span class="footer-brand-title d-block mb-2">MIDRAND BOOKS</span>
|
|
<p class="footer-desc mb-3">An architectural destination for curated print, thoughtful reading culture, and global design perspectives.</p>
|
|
<div class="d-flex flex-column gap-2">
|
|
<a href="mailto:info@midrandbooks.co.za" class="footer-contact-link">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><rect width="20" height="16" x="2" y="4" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /></svg>
|
|
info@midrandbooks.co.za
|
|
</a>
|
|
<a href="tel:+27872650198" class="footer-contact-link">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 .2.81.7A2 2 0 0 1 22 16.92z" /></svg>
|
|
+27 87 265 9463
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-6 col-lg-5 text-start">
|
|
<div class="row row-cols-2 justify-content-md-end g-3">
|
|
<div style="max-width: 160px;">
|
|
<span class="footer-section-heading d-block mb-2">PLATFORM</span>
|
|
<ul class="list-unstyled d-flex flex-column gap-1.5 m-0">
|
|
<li><a href="/about" class="footer-nav-link">About Us</a></li>
|
|
<li><a href="/" class="footer-nav-link">Browse Catalog</a></li>
|
|
<li><a href="/contact" class="footer-nav-link">Contact Us</a></li>
|
|
</ul>
|
|
</div>
|
|
<div style="max-width: 210px;">
|
|
<span class="footer-section-heading d-block mb-2">ENTERPRISE</span>
|
|
<ul class="list-unstyled d-flex flex-column gap-1.5 m-0">
|
|
<li class="footer-meta-item d-flex align-items-center gap-1.5">
|
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /></svg>
|
|
Lite Charms (PTY) Ltd
|
|
</li>
|
|
<li><a href="/terms" class="footer-nav-link">Terms & Conditions</a></li>
|
|
<li><a href="/privacy" class="footer-nav-link">Privacy Policy</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row align-items-center pt-3 footer-copyright-section">
|
|
<div class="col-12 text-center text-md-start">
|
|
<span class="footer-copyright">
|
|
© @DateTime.Now.Year Midrand Books. All rights reserved.
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
private string GlobalSearchQuery { get; set; } = string.Empty;
|
|
private bool IsSearchActive { get; set; } = false;
|
|
private bool IsCartOpen { get; set; } = false;
|
|
|
|
private List<CartItem> CartItems = new()
|
|
{
|
|
new CartItem { Id = 1, Title = "Letters from M/M (Paris)", Author = "M/M Paris", Price = 720, Quantity = 1 },
|
|
new CartItem { Id = 2, Title = "Daan Paans: Floating Signifiers", Author = "Daan Paans", Price = 540, Quantity = 1 },
|
|
new CartItem { Id = 3, Title = "Album Architectures, Maputo", Author = "Guedes Archive", Price = 350, Quantity = 1 }
|
|
};
|
|
|
|
private void ToggleGlobalSearch() => IsSearchActive = !IsSearchActive;
|
|
private void ToggleCart() => IsCartOpen = !IsCartOpen;
|
|
|
|
private void OnSearchInput(ChangeEventArgs e)
|
|
{
|
|
GlobalSearchQuery = e.Value?.ToString() ?? string.Empty;
|
|
}
|
|
|
|
private void ChangeQuantity(CartItem item, int delta)
|
|
{
|
|
item.Quantity += delta;
|
|
if (item.Quantity <= 0)
|
|
{
|
|
CartItems.Remove(item);
|
|
}
|
|
}
|
|
|
|
private void RemoveFromCart(CartItem item) => CartItems.Remove(item);
|
|
private int GetCartTotal() => CartItems.Sum(item => item.Price * item.Quantity);
|
|
|
|
private void RedirectToCart()
|
|
{
|
|
IsCartOpen = false;
|
|
Navigation.NavigateTo("/cart");
|
|
}
|
|
|
|
private void RedirectToCheckout()
|
|
{
|
|
IsCartOpen = false;
|
|
Navigation.NavigateTo("/checkout");
|
|
}
|
|
|
|
public class CartItem
|
|
{
|
|
public int Id { get; set; }
|
|
public string Title { get; set; } = string.Empty;
|
|
public string Author { get; set; } = string.Empty;
|
|
public int Price { get; set; }
|
|
public int Quantity { get; set; }
|
|
}
|
|
} |