Files
midrandbooks/MidrandBookshop/Components/Layout/MainLayout.razor
T
2026-07-18 12:08:14 +02:00

439 lines
16 KiB
Plaintext

@inherits LayoutComponentBase
@namespace MidrandBooks.Components.Layout
@using Microsoft.AspNetCore.Components
@using MidrandBooks.Components
@using MidrandBooks.Components.Pages
@using MidrandBooks.Models
<div class="relative min-h-screen flex flex-col justify-between selection:bg-violet-500/10 selection:text-violet-700">
<!-- Dynamic Ambient Motion Background -->
<AnimatedBackground />
<!-- Top Navigation Header -->
<Navbar
ActiveTab="@ActiveTab"
OnActiveTabChanged="HandleActiveTabChanged"
OnOpenCart="HandleOpenCart"
OnSelectBook="HandleSelectBook"
Books="CatalogBooks"
CartCount="CartItems.Count"
IsOfflineMode="IsOfflineMode"
OnOfflineModeToggle="HandleOfflineToggle"
/>
<!-- Main Central Work Space -->
<main class="flex-1 w-full max-w-7xl mx-auto px-4 sm:px-6 relative z-10 py-6">
@if (ActiveTab == "catalog")
{
<!-- Featured Slide Carousel Section -->
<FeaturedCarousel
FeaturedBooks="FeaturedBooks"
Purchases="PurchasedRecords"
Cart="CartItems"
OnSelectBook="HandleSelectBook"
OnReadEbook="HandleReadEbook"
OnStreamAudiobook="HandlePlayAudiobook"
OnAddToCart="HandleAddToCart"
/>
<!-- Main Interactive Catalog Grid -->
<BookGrid
Books="CatalogBooks"
Categories="Categories"
OnSelectBook="HandleSelectBook"
/>
}
else if (ActiveTab == "library")
{
<!-- Library View component -->
<LibraryView
Purchases="PurchasedRecords"
Books="CatalogBooks"
OfflineBooks="OfflineCachedBooks"
IsOfflineMode="IsOfflineMode"
OnSelectBook="HandleSelectBook"
OnReadBook="HandleReadEbook"
OnPlayBook="HandlePlayAudiobook"
OnCacheBookOffline="HandleCacheBookOffline"
/>
}
else if (ActiveTab == "author")
{
<!-- Author Portal component -->
<AuthorPortal
Categories="Categories"
OnAddCategory="HandleAddCategory"
OnPublishBook="HandlePublishBook"
/>
}
else if (ActiveTab == "terms")
{
<!-- Terms & Conditions view -->
<Terms />
}
else if (ActiveTab == "privacy")
{
<!-- Privacy Policy view -->
<Privacy />
}
<!-- Fallback or standard Blazor body placeholder -->
@Body
</main>
<!-- Detailed Book Information Modal Popup -->
@if (SelectedBook != null)
{
var isEbookOwned = PurchasedRecords.Any(p => p.BookId == SelectedBook.Id && p.Format == "ebook");
var isEbookCached = OfflineCachedBooks.Contains(SelectedBook.Id);
var isAudioOwned = PurchasedRecords.Any(p => p.BookId == SelectedBook.Id && p.Format == "audiobook");
var ebookInCart = CartItems.Any(i => i.Book.Id == SelectedBook.Id && i.Format == "ebook");
var audioInCart = CartItems.Any(i => i.Book.Id == SelectedBook.Id && i.Format == "audiobook");
<BookDetailModal
Book="SelectedBook"
IsEbookOwned="isEbookOwned"
IsEbookCached="isEbookCached"
IsAudioOwned="isAudioOwned"
EbookInCart="ebookInCart"
AudioInCart="audioInCart"
UserRating="UserRatings.ContainsKey(SelectedBook.Id) ? UserRatings[SelectedBook.Id] : (int?)null"
OnClose="HandleCloseModal"
OnAddToCart="HandleAddToCartFromModal"
OnReadEbook="() => HandleReadEbook(SelectedBook)"
OnPlayAudiobook="() => HandlePlayAudiobook(SelectedBook)"
OnRateBook="rating => HandleRateBook(SelectedBook.Id, rating)"
/>
}
<!-- Secure Checkout Side Panel Drawer -->
<CartAndCheckout
IsOpen="IsCartOpen"
OnClose="HandleCloseCart"
CartItems="CartItems"
OnRemoveFromCart="HandleRemoveFromCart"
OnCheckoutSuccess="HandleCheckoutSuccess"
/>
<!-- Embedded Proprietary Ebook Reader Overlay -->
@if (ActiveReadingBook != null)
{
<EbookReader
Book="ActiveReadingBook"
CurrentPage="ActiveReadingPage"
OnClose="HandleCloseReader"
OnPageChanged="HandlePageProgressChanged"
/>
}
<!-- Persistent Bottom Audiobook Player Node -->
@if (ActivePlayingBook != null)
{
<AudiobookPlayer
Book="ActivePlayingBook"
IsPlaying="IsAudioPlaying"
OnPlayToggle="HandleAudioPlayToggle"
OnClose="HandleCloseAudioPlayer"
/>
}
<!-- Global High Contrast Footer -->
<footer class="border-t border-white/5 bg-slate-950 mt-16 py-12 relative z-10">
<div class="max-w-7xl mx-auto px-4 sm:px-6 flex flex-col md:flex-row items-center justify-between gap-6 text-center md:text-left">
<div>
<span class="font-sans text-sm font-extrabold text-white tracking-tight uppercase">
Midrand<span class="font-light text-slate-300">books</span><span class="text-[10px] font-normal lowercase text-violet-400">.co.za</span>
</span>
<p class="text-[11px] text-slate-300 mt-1.5 font-sans">
Legal Deposit Compliant • National Library Services of South Africa • Est. 2026
</p>
</div>
<div class="flex flex-wrap items-center justify-center gap-6 text-[11px] font-mono font-bold uppercase text-slate-300 tracking-wider">
<button @onclick='() => HandleActiveTabChanged("catalog")' class="hover:text-white transition-colors cursor-pointer focus:outline-none">Catalog</button>
<button @onclick='() => HandleActiveTabChanged("terms")' class="hover:text-white transition-colors cursor-pointer focus:outline-none text-violet-400">Terms of Sale</button>
<button @onclick='() => HandleActiveTabChanged("privacy")' class="hover:text-white transition-colors cursor-pointer focus:outline-none text-violet-400">Privacy Policy</button>
<a href="#contact" class="hover:text-white transition-colors">Midrand, Gauteng</a>
</div>
<div class="flex flex-col items-center md:items-end gap-1">
<p class="text-[10px] text-slate-400 font-mono">
© 2026 Midrand Books. Securely compiled under sandbox rules.
</p>
<p class="text-[10px] text-slate-500 font-mono">
Designed, maintained, owned &amp; hosted by <a href="https://www.litecharms.co.za" target="_blank" rel="noopener noreferrer" class="text-violet-400 hover:text-violet-300 underline">LiteCharms (PTY) Ltd</a>
</p>
</div>
</div>
</footer>
<!-- Signal Interruption Reconnect Modal Overlay -->
<ReconnectModal />
<!-- Cookie Acceptance Banner and Privacy Modal -->
<CookieBanner />
</div>
@code {
private string ActiveTab { get; set; } = "catalog";
private bool IsCartOpen { get; set; } = false;
private bool IsOfflineMode { get; set; } = false;
// Master list of categories
private List<string> Categories { get; set; } = new()
{
"Fiction", "Non-Fiction", "African Literature", "Poetry", "Academic & History", "Biography"
};
// Books and States
private List<BookModel> CatalogBooks { get; set; } = new();
private List<BookModel> FeaturedBooks { get; set; } = new();
private BookModel? SelectedBook { get; set; }
private List<CartItem> CartItems { get; set; } = new();
private List<PurchaseRecord> PurchasedRecords { get; set; } = new();
private List<string> OfflineCachedBooks { get; set; } = new();
private Dictionary<string, int> UserRatings { get; set; } = new();
// Active Reader/Player Overlays
private BookModel? ActiveReadingBook { get; set; }
private int ActiveReadingPage { get; set; } = 0;
private BookModel? ActivePlayingBook { get; set; }
private bool IsAudioPlaying { get; set; } = false;
protected override void OnInitialized()
{
// Load default premium seed publications
var random = new Random(2026);
var categoriesList = new[] { "Fiction", "African Literature", "Poetry", "Academic & History", "Biography", "Non-Fiction" };
var colors = new[] {
"from-indigo-900 to-purple-950",
"from-rose-950 to-orange-900",
"from-slate-900 to-teal-950",
"from-amber-950 to-emerald-950"
};
var titles = new[] {
"Whispers of the Highveld", "Gauteng Gold Rush Chronicles", "Soweto Symphony of Dreams",
"Limpopo Rain Queen", "The Karoo Echoes", "Table Mountain Sunrise",
"Durban Beachfront Rhythms", "Zululand Warriors"
};
var authors = new[] {
"N. Khumalo", "S. Ndlovu", "T. Mokoena", "K. Naidoo", "M. van der Merwe",
"L. Sibanda", "J. Govender", "Z. Zuma"
};
var descriptions = new[] {
"An epic South African saga highlighting historical transformations, personal sacrifices, and cultural richness in the Gauteng plains.",
"A comprehensive investigative review documenting the rich socio-economic changes driven by gold discoveries in early Johannesburg.",
"A beautifully written modern poetry collection exploring life, struggle, and celebratory musical triumphs in South Africa's heartland.",
"An enchanting fantasy drawing on classical folklore, rainmaking traditions, and political intrigue in the northern provinces.",
"A mystery novel detailing hidden family histories buried deep in the silent, expansive landscapes of the great Karoo desert."
};
for (int i = 0; i < titles.Length; i++)
{
var book = new BookModel
{
Id = $"book-seed-{i + 1}",
Title = titles[i],
Author = authors[i],
AuthorId = $"seed-auth-{i + 1}",
Description = descriptions[i % descriptions.Length],
Price = random.Next(95, 295),
Category = categoriesList[i % categoriesList.Length],
CoverColor = colors[i % colors.Length],
Rating = 4.0 + random.NextDouble(),
ReviewsCount = random.Next(5, 85),
PublishedYear = random.Next(2018, 2026),
PagesCount = random.Next(120, 480),
AudioDuration = $"{random.Next(4, 12)}h {random.Next(10, 59)}m",
Narrator = authors[(i + 2) % authors.Length],
Bestseller = i < 2,
Trending = i >= 2 && i < 4,
Status = "published"
};
// Populate some dummy chapters
book.FullText = new List<string>
{
$"Chapter 1 of {book.Title}.\n\nThe morning sun breaks across the rolling hills of the Highveld, painting the Gauteng skies in shades of gold and amber. The dusty streets of Midrand begin to hum with the early morning life, as commuters fill the platforms and local merchants raise their shutters.\n\nIt was here that Khwezi first realized that the stories written in these lands had a rhythm of their own—a heartbeat that echoed the rich histories of those who walked before.",
"Chapter 2.\n\nAs the afternoon heat settles over the vast landscapes, a quiet stillness descends. Under the shade of an ancient thorn tree, old journals are opened, revealing handwritten accounts of early migrations, family secrets, and triumphant breakthroughs. The digital DRM safeguards preserve these fragile pages for generations to come."
};
CatalogBooks.Add(book);
}
FeaturedBooks = CatalogBooks.Where(b => b.Bestseller || b.Trending).Take(3).ToList();
// Pre-purchase one book to show active items in Library immediately
PurchasedRecords.Add(new PurchaseRecord
{
BookId = "book-seed-3",
Format = "ebook",
CurrentPage = 0,
Progress = 15
});
}
private void HandleActiveTabChanged(string tab)
{
ActiveTab = tab;
}
private void HandleOpenCart()
{
IsCartOpen = true;
}
private void HandleCloseCart()
{
IsCartOpen = false;
}
private void HandleOfflineToggle(bool value)
{
IsOfflineMode = value;
}
private void HandleSelectBook(BookModel book)
{
SelectedBook = book;
}
private void HandleCloseModal()
{
SelectedBook = null;
}
private void HandleAddToCart((BookModel Book, string Format) tuple)
{
if (!CartItems.Any(i => i.Book.Id == tuple.Book.Id && i.Format == tuple.Format))
{
CartItems.Add(new CartItem { Book = tuple.Book, Format = tuple.Format });
}
}
private void HandleAddToCartFromModal(string format)
{
if (SelectedBook != null)
{
HandleAddToCart((SelectedBook, format));
}
}
private void HandleRemoveFromCart(CartItem item)
{
CartItems.Remove(item);
}
private void HandleCheckoutSuccess(List<CartItem> items)
{
foreach (var item in items)
{
if (!PurchasedRecords.Any(p => p.BookId == item.Book.Id && p.Format == item.Format))
{
PurchasedRecords.Add(new PurchaseRecord
{
BookId = item.Book.Id,
Format = item.Format,
CurrentPage = 0,
Progress = 0
});
}
}
CartItems.Clear();
ActiveTab = "library";
}
private void HandleReadEbook(BookModel book)
{
ActiveReadingBook = book;
var purchase = PurchasedRecords.FirstOrDefault(p => p.BookId == book.Id && p.Format == "ebook");
ActiveReadingPage = purchase?.CurrentPage ?? 0;
SelectedBook = null; // Close detail modal if open
}
private void HandleCloseReader()
{
ActiveReadingBook = null;
}
private void HandlePageProgressChanged(int page)
{
ActiveReadingPage = page;
if (ActiveReadingBook != null)
{
var purchase = PurchasedRecords.FirstOrDefault(p => p.BookId == ActiveReadingBook.Id && p.Format == "ebook");
if (purchase != null)
{
purchase.CurrentPage = page;
}
}
}
private void HandlePlayAudiobook(BookModel book)
{
ActivePlayingBook = book;
IsAudioPlaying = true;
SelectedBook = null; // Close detail modal if open
}
private void HandleCloseAudioPlayer()
{
ActivePlayingBook = null;
IsAudioPlaying = false;
}
private void HandleAudioPlayToggle(bool isPlaying)
{
IsAudioPlaying = isPlaying;
}
private void HandleCacheBookOffline(string bookId)
{
if (!OfflineCachedBooks.Contains(bookId))
{
OfflineCachedBooks.Add(bookId);
}
}
private void HandleAddCategory(string category)
{
if (!Categories.Contains(category))
{
Categories.Add(category);
}
}
private void HandlePublishBook(BookModel book)
{
CatalogBooks.Insert(0, book);
}
private void HandleRateBook(string bookId, int rating)
{
int? oldRating = UserRatings.ContainsKey(bookId) ? UserRatings[bookId] : null;
UserRatings[bookId] = rating;
var book = CatalogBooks.FirstOrDefault(b => b.Id == bookId);
if (book != null)
{
double oldTotal = book.Rating * book.ReviewsCount;
if (oldRating.HasValue)
{
book.Rating = (oldTotal - oldRating.Value + rating) / Math.Max(1, book.ReviewsCount);
}
else
{
book.ReviewsCount += 1;
book.Rating = (oldTotal + rating) / book.ReviewsCount;
}
book.Rating = Math.Round(book.Rating * 10) / 10;
}
}
}