Spaces:
Sleeping
Sleeping
File size: 14,873 Bytes
cf599e0 0acba57 cf599e0 6aad87c cf599e0 6aad87c cf599e0 0acba57 1fe511c 0acba57 cf9e5bb 0acba57 cf599e0 6aad87c 0acba57 6aad87c 0acba57 6aad87c 0acba57 6aad87c 0acba57 6aad87c 0acba57 cf599e0 6aad87c cf599e0 6aad87c cf599e0 6aad87c cf599e0 ccc2569 cf599e0 ccc2569 0acba57 cf599e0 0acba57 6aad87c 0acba57 cf599e0 0acba57 ccc2569 0acba57 ccc2569 0acba57 ccc2569 0acba57 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | @inherits LayoutComponentBase
@using BlazorFrontend.Providers
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthStateProvider
<div class="flex h-full overflow-hidden">
<!-- Sidebar -->
<aside class="hidden md:flex md:w-64 md:flex-col md:fixed md:inset-y-0 z-50">
<div class="flex flex-col flex-grow border-r border-mystic-200 bg-white pt-5 pb-4 overflow-y-auto">
<div class="flex items-center flex-shrink-0 px-4 gap-2">
<div class="bg-mystic-900 rounded-lg p-1.5 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" 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-lg font-bold tracking-tight">Library OS</span>
</div>
<div class="mt-8 flex-grow flex flex-col">
<nav class="flex-1 px-2 space-y-1">
<NavLink href="" Match="NavLinkMatch.All" class="nav-link" ActiveClass="active">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="15" rx="1"/></svg>
Dashboard
</NavLink>
<NavLink href="Books" class="nav-link" ActiveClass="active">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>
Books Catalog
</NavLink>
<AuthorizeView>
<NotAuthorized>
<NavLink href="Borrowings" class="nav-link" ActiveClass="active">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="m21 21-6-6m6 6v-4.8m0 4.8h-4.8"/><path d="M3 16.2V21m0 0h4.8M3 21l6-6"/><path d="M21 7.8V3m0 0h-4.8M21 3l-6 6"/><path d="M3 7.8V3m0 0h4.8M3 3l6 6"/></svg>
Active Loans
</NavLink>
</NotAuthorized>
</AuthorizeView>
<AuthorizeView>
<Authorized Context="authContext">
@if (!authContext.User.IsInRole("Librarian"))
{
<NavLink href="Borrowings" class="nav-link" ActiveClass="active">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="m21 21-6-6m6 6v-4.8m0 4.8h-4.8"/><path d="M3 16.2V21m0 0h4.8M3 21l6-6"/><path d="M21 7.8V3m0 0h-4.8M21 3l-6 6"/><path d="M3 7.8V3m0 0h4.8M3 3l6 6"/></svg>
Active Loans
</NavLink>
<NavLink href="Membership" class="nav-link" ActiveClass="active">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/></svg>
My Membership
</NavLink>
<NavLink href="Rewards" class="nav-link" ActiveClass="active">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M20 7h-9"/><path d="M14 17H5"/><circle cx="17" cy="17" r="3"/><circle cx="7" cy="7" r="3"/></svg>
Rewards
</NavLink>
<NavLink href="Wishlist" class="nav-link" ActiveClass="active">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" /></svg>
Wishlist
</NavLink>
}
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="Librarian">
<Authorized>
<NavLink href="Members" class="nav-link" ActiveClass="active">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
Members
</NavLink>
<NavLink href="Borrowings/Manage" class="nav-link" ActiveClass="active">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M11 15h2a2 2 0 1 0 0-4h-2a2 2 0 1 1 0-4h2"/><path d="M12 5v14"/><path d="M18 19V5l-4 4"/><circle cx="12" cy="12" r="10"/></svg>
Manage Borrowings
</NavLink>
<NavLink href="Rewards/Manage" class="nav-link" ActiveClass="active">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
Manage Rewards
</NavLink>
<NavLink href="Insights" class="nav-link" ActiveClass="active">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M21 12V7H5a2 2 0 010-4h14v4"/><path d="M3 5v14a2 2 0 002 2h16v-5"/><path d="M18 12a2 2 0 000 4h4v-4Z"/></svg>
User Insights
</NavLink>
</Authorized>
</AuthorizeView>
</nav>
</div>
<div class="px-4 py-4 border-t border-mystic-100">
<AuthorizeView>
<Authorized Context="authContext">
<div class="flex items-center gap-3">
<div class="h-8 w-8 rounded-full bg-mystic-900 flex items-center justify-center text-white font-bold text-xs uppercase">
@(authContext.User.Identity?.Name?.Substring(0, 2) ?? "US")
</div>
<div class="flex flex-col">
<span class="text-sm font-medium">@authContext.User.Identity?.Name</span>
<span class="text-xs text-mystic-500">@authContext.User.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Role || c.Type == "role")?.Value</span>
</div>
</div>
</Authorized>
<NotAuthorized>
<div class="flex items-center gap-3">
<div class="h-8 w-8 rounded-full bg-mystic-900 flex items-center justify-center text-white font-bold text-xs uppercase">
GU
</div>
<div class="flex flex-col">
<span class="text-sm font-medium">Guest User</span>
<span class="text-xs text-mystic-500">Sign in to borrow</span>
</div>
</div>
</NotAuthorized>
</AuthorizeView>
</div>
</div>
</aside>
<!-- Main Content Wrapper -->
<div class="md:pl-64 flex flex-col flex-1 w-0">
<!-- Topbar -->
<header class="flex-shrink-0 flex h-16 bg-white border-b border-mystic-200">
<div class="flex-1 px-4 flex justify-between">
<div class="flex-1 flex items-center">
<div class="w-full max-w-sm">
<label for="search" class="sr-only">Search</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-4 w-4 text-mystic-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
</svg>
</div>
<input id="search" name="search" class="block w-full pl-10 pr-3 py-2 border border-mystic-200 rounded-md leading-5 bg-mystic-50 placeholder-mystic-400 focus:outline-none focus:ring-1 focus:ring-mystic-900 focus:border-mystic-900 sm:text-sm transition-all" placeholder="Search books, authors, ISBN..." type="search">
</div>
</div>
</div>
<div class="ml-4 flex items-center md:ml-6 gap-4">
<a href="notifications" class="relative bg-white p-1 rounded-full text-mystic-400 hover:text-mystic-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-mystic-900">
<span class="sr-only">View notifications</span>
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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" />
</svg>
@if (_unreadCount > 0)
{
<span class="absolute top-0 right-0 block h-4 w-4 rounded-full bg-rose-500 text-[10px] font-bold text-white flex items-center justify-center border-2 border-white transform translate-x-1/2 -translate-y-1/2">
@(_unreadCount > 9 ? "9+" : _unreadCount)
</span>
}
</a>
<AuthorizeView>
<Authorized>
<a href="/logout" class="btn-secondary py-1.5">Logout</a>
</Authorized>
<NotAuthorized>
<a href="/login" class="btn-primary py-1.5">Sign In</a>
</NotAuthorized>
</AuthorizeView>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-1 relative overflow-y-auto focus:outline-none">
<div class="py-8 px-4 sm:px-6 md:px-8 max-w-7xl mx-auto">
@if (_isReady)
{
@Body
}
else
{
<div class="flex items-center justify-center h-64">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-mystic-900"></div>
</div>
}
</div>
</main>
</div>
</div>
<div id="blazor-error-ui" class="hidden">
An unhandled error has occurred.
<a href="" class="reload text-blue-500 hover:underline">Reload</a>
<a class="dismiss cursor-pointer">🗙</a>
</div>
@inject BlazorFrontend.Services.LibraryApiClient ApiClient
@inject IJSRuntime JS
@code {
private bool _isReady;
private int _unreadCount;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var jwtProvider = AuthStateProvider as JwtAuthenticationStateProvider;
if (jwtProvider != null)
{
await jwtProvider.LoadTokenAsync();
// Fetch unread count if authenticated
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity?.IsAuthenticated == true)
{
_unreadCount = await ApiClient.GetUnreadNotificationCountAsync();
// Update FCM Token
try
{
var token = await JS.InvokeAsync<string>("window.getFcmToken");
if (!string.IsNullOrEmpty(token))
{
await ApiClient.UpdateFcmTokenAsync(token);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error updating FCM token: {ex.Message}");
}
}
}
_isReady = true;
StateHasChanged();
}
}
}
|