Files
midrandbooks/MidrandBookshop/Components/Layout/MainLayout.razor
T
2026-05-23 16:52:27 +02:00

318 lines
21 KiB
Plaintext

@inherits LayoutComponentBase
@inject NavigationManager Navigation
<div class="position-relative min-vh-100 d-flex flex-column justify-content-between overflow-hidden" style="background-color: #F9F9F9;">
<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>
<div class="w-100 flex-grow-1 position-relative">
<div class="position-absolute top-0 start-0 overflow-hidden d-none d-md-block" style="z-index: 0; 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 sticky-top-nav" 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="/login" 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>
<main class="position-relative" style="z-index: 5;">
<CascadingValue Value="GlobalSearchQuery">
@Body
</CascadingValue>
</main>
</div>
<footer class="w-100 position-relative overflow-hidden"
style="z-index: 10; padding: 5.5rem 0 3.5rem 0; background-color: #F2F4F7 !important; background-image: linear-gradient(90deg, #FFF 0%, rgba(243,245,249,0.98) 22%, #FFF 48%, rgba(239,242,247,0.98) 73%, #FFF 100%), repeating-linear-gradient(15deg, rgba(0,0,0,0.005) 0px, rgba(0,0,0,0.005) 1px, transparent 1px, transparent 41px), repeating-linear-gradient(-25deg, rgba(255,255,255,0.6) 0px, rgba(255,255,255,0.6) 2px, transparent 2px, transparent 73px), repeating-linear-gradient(65deg, rgba(0,0,0,0.003) 0px, rgba(0,0,0,0.003) 1px, transparent 1px, transparent 127px) !important;">
<div class="position-absolute top-0 start-0 w-100 h-100" style="background: radial-gradient(circle at 50% 30%, rgba(255,255,255,0.45) 0%, rgba(242,244,248,0.05) 100%); pointer-events: none; z-index: 1; backdrop-filter: blur(1.5px); -webkit-backdrop-filter: blur(1.5px);"></div>
<div class="position-absolute top-0 start-0 w-100 h-100" style="z-index: 2; opacity: 0.95; pointer-events: none; mix-blend-mode: multiply;">
<svg width="100%" height="100%" viewBox="0 0 1440 400" preserveAspectRatio="none" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="15%" cy="35%" rx="750" ry="400" fill="url(#randomMetal_1)" />
<ellipse cx="60%" cy="80%" rx="950" ry="350" fill="url(#randomMetal_2)" />
<ellipse cx="85%" cy="20%" rx="600" ry="250" fill="url(#randomMetal_3)" />
<defs>
<radialGradient id="randomMetal_1" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(216 140) rotate(15) scale(350 750)">
<stop offset="0%" stop-color="#FFFFFF" />
<stop offset="60%" stop-color="#EBF0F7" />
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0" />
</radialGradient>
<radialGradient id="randomMetal_2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(864 320) rotate(-15) scale(300 950)">
<stop offset="0%" stop-color="#FFFFFF" />
<stop offset="70%" stop-color="#E2E7F2" />
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0" />
</radialGradient>
<radialGradient id="randomMetal_3" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(1224 80) rotate(45) scale(200 600)">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.8" />
<stop offset="50%" stop-color="#E9EDF5" stop-opacity="0.3" />
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0" />
</radialGradient>
</defs>
</svg>
</div>
<div class="position-absolute top-0 start-0 w-100" style="height: 2px; z-index: 5;">
<div class="w-100" style="height: 1px; background: rgba(0, 0, 0, 0.055);"></div>
<div class="w-100" style="height: 1px; background: rgba(255, 255, 255, 0.95);"></div>
</div>
<div class="container-fluid px-md-5 position-relative" style="z-index: 4;">
<div class="row g-4 pb-5 justify-content-between" style="border-bottom: 1px solid rgba(0, 0, 0, 0.05);">
<div class="col-12 col-md-5">
<span class="fw-bold tracking-tight text-dark d-block mb-3" style="font-size: 0.75rem; letter-spacing: 1px; font-family: 'Inter', sans-serif;">MIDRAND BOOKS</span>
<p class="text-muted small mb-4" style="line-height: 1.6; max-width: 360px;">An architectural destination for curated print, thoughtful reading culture, and global design perspectives.</p>
<div class="d-flex flex-column gap-2.5">
<a href="mailto:info@midrandbooks.co.za" class="text-decoration-none text-muted d-inline-flex align-items-center gap-2 small">
<svg width="13" height="13" 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="text-decoration-none text-muted d-inline-flex align-items-center gap-2 small">
<svg width="13" height="13" 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="text-dark fw-bold tracking-wider d-block mb-3" style="font-size: 0.65rem; letter-spacing: 1.2px;">PLATFORM</span>
<ul class="list-unstyled d-flex flex-column gap-2 m-0">
<li><a href="/about" class="text-decoration-none text-muted small">About Us</a></li>
<li><a href="/" class="text-decoration-none text-muted small">Browse Catalog</a></li>
<li><a href="/contact" class="text-decoration-none text-muted small">Contact Us</a></li>
</ul>
</div>
<div style="max-width: 210px;">
<span class="text-dark fw-bold tracking-wider d-block mb-3" style="font-size: 0.65rem; letter-spacing: 1.2px;">ENTERPRISE</span>
<ul class="list-unstyled d-flex flex-column gap-2 m-0">
<li class="small text-muted d-flex align-items-center gap-1.5 mb-1" style="font-weight: 500;">
<svg width="12" height="12" 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="text-decoration-none text-muted small">Terms & Conditions</a></li>
<li><a href="/privacy" class="text-decoration-none text-muted small">Privacy Policy</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="row align-items-center pt-4">
<div class="col-12 text-center text-md-start">
<span class="text-muted tracking-wide" style="font-size: 0.75rem; font-weight: 500;">
&copy; @DateTime.Now.Year Midrand Books. All rights reserved.
</span>
</div>
</div>
</div>
</footer>
</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; }
}
}