Spaces:
Sleeping
Sleeping
| @inherits LayoutComponentBase | |
| @implements IDisposable | |
| @using BlazorWebAssembly.Services | |
| @inject ThemeService ThemeService | |
| @inject AuthenticationStateProvider AuthStateProvider | |
| @inject IJSRuntime JS | |
| @inject LibraryApiClient ApiClient | |
| @inject NotificationStateService NotifService | |
| <AuthorizeView> | |
| <Authorized> | |
| <div class="flex h-screen overflow-hidden bg-body text-main animate-fade font-sans"> | |
| <!-- Sidebar (Dashboard View) --> | |
| <aside class="hidden lg:flex lg:w-64 lg:flex-col lg:fixed lg:inset-y-0 z-50 border-r border-border bg-card"> | |
| <div class="flex flex-col flex-grow pt-8 pb-4 overflow-y-auto"> | |
| <div class="flex items-center flex-shrink-0 px-6 gap-3 mb-10"> | |
| <div class="bg-primary rounded-xl p-2 text-white shadow-lg shadow-primary/20"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m16 6 4 14"/><path d="M12 6v14"/><path d="M8 8v12"/><path d="M4 4v16"/><path d="M4 12V4l4 4"/><path d="m12 6 4 4"/></svg> | |
| </div> | |
| <span class="text-xl font-black tracking-tighter uppercase">LibraryLuxe</span> | |
| </div> | |
| <div class="flex-grow px-3"> | |
| <NavMenu /> | |
| </div> | |
| <div class="px-6 py-6 border-t border-border"> | |
| <div class="mb-6"> | |
| <p class="text-[10px] font-black text-muted uppercase tracking-[0.2em] mb-4 text-center">Switch Theme</p> | |
| <ThemeSwitcher /> | |
| </div> | |
| <div class="flex items-center gap-3 p-3 bg-body rounded-2xl border border-border"> | |
| <div class="h-10 w-10 rounded-xl bg-primary flex items-center justify-center text-white font-bold shadow-sm"> | |
| @(context.User.Identity?.Name?.Length > 0 ? context.User.Identity.Name[..1].ToUpper() : "?") | |
| </div> | |
| <div class="flex flex-col min-w-0"> | |
| <span class="text-xs font-bold truncate">@context.User.Identity?.Name</span> | |
| <span class="text-[10px] text-muted font-medium uppercase tracking-wider">@(context.User.IsInRole("Librarian") ? "Librarian" : "Member")</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </aside> | |
| <div class="lg:pl-64 flex flex-col flex-1 w-0"> | |
| <header class="h-20 bg-card/80 backdrop-blur-md border-b border-border sticky top-0 z-40 flex items-center justify-between px-8"> | |
| <div class="flex-grow max-w-lg"> | |
| <div class="relative"> | |
| <svg class="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg> | |
| <input type="text" placeholder="Quick search..." class="w-full bg-body border border-border rounded-xl pl-12 pr-4 py-2.5 text-sm focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all shadow-sm" /> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-6"> | |
| <NavLink href="/notifications" class="relative text-muted hover:text-primary transition-colors p-2 rounded-xl hover:bg-primary/5"> | |
| <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path></svg> | |
| @if (NotifService.UnreadCount > 0) | |
| { | |
| <span class="absolute -top-0.5 -right-0.5 flex h-5 w-5 items-center justify-center bg-primary text-white text-[9px] font-black rounded-full border-2 border-card shadow-md animate-bounce z-10"> | |
| @(NotifService.UnreadCount > 9 ? "9+" : NotifService.UnreadCount) | |
| </span> | |
| } | |
| </NavLink> | |
| <a href="/logout" class="text-xs font-black uppercase tracking-widest text-muted hover:text-red-500 transition-colors">Logout</a> | |
| </div> | |
| </header> | |
| <main class="flex-1 overflow-y-auto p-8 scrollbar-hide"> | |
| <div class="max-w-7xl mx-auto w-full"> | |
| @Body | |
| </div> | |
| </main> | |
| </div> | |
| </div> | |
| </Authorized> | |
| <NotAuthorized> | |
| <div class="min-h-screen bg-body text-main animate-fade font-sans flex flex-col"> | |
| <!-- Top Navbar (Guest View) --> | |
| <nav class="h-20 bg-card/80 backdrop-blur-md border-b border-border sticky top-0 z-50 px-8 flex items-center justify-between shadow-sm"> | |
| <div class="flex items-center gap-10"> | |
| <a href="/" class="flex items-center gap-3 group"> | |
| <div class="bg-primary rounded-xl p-2 text-white shadow-lg shadow-primary/20 group-hover:scale-110 transition-transform"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m16 6 4 14"/><path d="M12 6v14"/><path d="M8 8v12"/><path d="M4 4v16"/><path d="M4 12V4l4 4"/><path d="m12 6 4 4"/></svg> | |
| </div> | |
| <span class="text-xl font-black tracking-tighter uppercase">LibraryLuxe</span> | |
| </a> | |
| <div class="hidden md:flex items-center gap-8"> | |
| <NavLink href="/" Match="NavLinkMatch.All" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">Home</NavLink> | |
| <NavLink href="/membership" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">Membership</NavLink> | |
| <NavLink href="/features" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">Features</NavLink> | |
| <NavLink href="/about" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">About Us</NavLink> | |
| <NavLink href="/contact" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">Contact</NavLink> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-6"> | |
| <ThemeSwitcher /> | |
| <div class="h-6 w-px bg-border hidden sm:block"></div> | |
| <div class="flex items-center gap-4"> | |
| <a href="/login" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">Sign In</a> | |
| <a href="/register" class="bg-primary text-white px-6 py-2.5 rounded-xl text-xs font-black uppercase tracking-widest hover:bg-primary-hover shadow-lg shadow-primary/20 transition-all transform hover:scale-105">Get Started</a> | |
| </div> | |
| </div> | |
| </nav> | |
| <main class="flex-grow"> | |
| @Body | |
| </main> | |
| <footer class="py-20 border-t border-border bg-card"> | |
| <div class="max-w-7xl mx-auto px-8"> | |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-12 mb-16"> | |
| <div class="col-span-1 md:col-span-2"> | |
| <div class="flex items-center gap-3 mb-6"> | |
| <div class="bg-primary rounded-xl p-2 text-white"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m16 6 4 14"/><path d="M12 6v14"/><path d="M8 8v12"/><path d="M4 4v16"/><path d="M4 12V4l4 4"/><path d="m12 6 4 4"/></svg> | |
| </div> | |
| <span class="text-xl font-black tracking-tighter uppercase">LibraryLuxe</span> | |
| </div> | |
| <p class="text-muted text-sm max-w-sm leading-relaxed mb-8"> | |
| Redefining the modern library experience with digital-first solutions and community-driven rewards. | |
| </p> | |
| <div class="flex gap-4"> | |
| <!-- Social Icons Placeholder --> | |
| <div class="w-10 h-10 rounded-xl bg-body border border-border flex items-center justify-center text-muted hover:text-primary transition-colors cursor-pointer"> | |
| <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"/></svg> | |
| </div> | |
| <div class="w-10 h-10 rounded-xl bg-body border border-border flex items-center justify-center text-muted hover:text-primary transition-colors cursor-pointer"> | |
| <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/></svg> | |
| </div> | |
| </div> | |
| </div> | |
| <div> | |
| <h4 class="text-xs font-black uppercase tracking-[0.2em] text-main mb-6">Navigation</h4> | |
| <ul class="space-y-4"> | |
| <li><a href="/" class="text-sm text-muted hover:text-primary transition-colors">Home</a></li> | |
| <li><a href="/membership" class="text-sm text-muted hover:text-primary transition-colors">Membership</a></li> | |
| <li><a href="/features" class="text-sm text-muted hover:text-primary transition-colors">Features</a></li> | |
| <li><a href="/faq" class="text-sm text-muted hover:text-primary transition-colors">FAQ</a></li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4 class="text-xs font-black uppercase tracking-[0.2em] text-main mb-6">Legal</h4> | |
| <ul class="space-y-4"> | |
| <li><a href="/privacy" class="text-sm text-muted hover:text-primary transition-colors">Privacy Policy</a></li> | |
| <li><a href="/terms" class="text-sm text-muted hover:text-primary transition-colors">Terms of Service</a></li> | |
| <li><a href="/contact" class="text-sm text-muted hover:text-primary transition-colors">Contact Support</a></li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="pt-8 border-t border-border flex flex-col md:row justify-between items-center gap-4"> | |
| <p class="text-[10px] font-bold text-muted uppercase tracking-[0.2em]">© 2026 LibraryLuxe Ecosystem. Built with passion for readers.</p> | |
| <div class="flex items-center gap-2"> | |
| <div class="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div> | |
| <span class="text-[10px] font-black uppercase tracking-widest text-muted">System Operational</span> | |
| </div> | |
| </div> | |
| </div> | |
| </footer> | |
| </div> | |
| </NotAuthorized> | |
| </AuthorizeView> | |
| @code { | |
| protected override async Task OnInitializedAsync() | |
| { | |
| await ThemeService.InitializeAsync(); | |
| AuthStateProvider.AuthenticationStateChanged += OnAuthStateChanged; | |
| NotifService.OnChange += HandleNotifChange; | |
| _ = UpdateNotificationCount(); | |
| } | |
| private async void OnAuthStateChanged(Task<AuthenticationState> task) | |
| { | |
| await InvokeAsync(UpdateNotificationCount); | |
| } | |
| private async Task UpdateNotificationCount() | |
| { | |
| try | |
| { | |
| var authState = await AuthStateProvider.GetAuthenticationStateAsync(); | |
| if (authState.User.Identity?.IsAuthenticated == true) | |
| { | |
| var count = await ApiClient.GetUnreadNotificationCountAsync(); | |
| NotifService.SetUnreadCount(count); | |
| // Also update FCM token if not already done | |
| var token = await JS.InvokeAsync<string>("getFcmToken"); | |
| if (!string.IsNullOrEmpty(token)) | |
| { | |
| await ApiClient.UpdateFcmTokenAsync(token); | |
| } | |
| } | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"[MainLayout] Error updating notifications: {ex.Message}"); | |
| } | |
| } | |
| protected override void OnAfterRender(bool firstRender) | |
| { | |
| // No longer using OnAfterRender for initial fetch as it's handled in OnInitialized and events | |
| } | |
| private async void HandleNotifChange() => await InvokeAsync(StateHasChanged); | |
| public void Dispose() | |
| { | |
| AuthStateProvider.AuthenticationStateChanged -= OnAuthStateChanged; | |
| NotifService.OnChange -= HandleNotifChange; | |
| } | |
| } | |