Spaces:
Sleeping
Sleeping
File size: 12,262 Bytes
1fe511c 4433399 1fe511c ccc2569 1fe511c ccc2569 1fe511c ccc2569 1fe511c ccc2569 1fe511c ccc2569 1fe511c ccc2569 1fe511c ccc2569 1fe511c ccc2569 1fe511c ccc2569 1fe511c ccc2569 1fe511c ccc2569 1fe511c ccc2569 1fe511c ccc2569 1fe511c 4433399 | 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 | @page "/Wishlist"
@using BlazorFrontend.Services
@using LibraryManagement.Shared.Models
@using Microsoft.AspNetCore.Authorization
@inject WishlistService WishlistService
@inject IJSRuntime JS
@inject NavigationManager Navigation
<PageTitle>My Wishlist & Favourites</PageTitle>
<AuthorizeView Roles="Member">
<Authorized>
<div class="mb-8 flex flex-col md:flex-row md:items-center justify-between gap-6 animate-in fade-in slide-in-from-top-4 duration-500">
<div>
<h1 class="text-3xl font-extrabold tracking-tight text-mystic-900">My Collections</h1>
<p class="text-mystic-600 mt-1">Track books you want to borrow and your all-time favourites.</p>
</div>
</div>
<div class="mb-6 border-b border-mystic-200">
<nav class="-mb-px flex gap-6">
<button @onclick='() => _activeTab = "wishlist"'
class='pb-3 text-sm font-semibold border-b-2 transition-all @(_activeTab == "wishlist" ? "border-amber-500 text-amber-600" : "border-transparent text-mystic-400 hover:text-amber-500")'>
<span class="inline-flex items-center gap-1.5">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
</svg>
Wishlist
@if (_wishlist.Any())
{
<span class="ml-1 px-1.5 py-0.5 rounded-full bg-amber-500 text-white text-[10px]">@_wishlist.Count</span>
}
</span>
</button>
<button @onclick='() => _activeTab = "favourites"'
class='pb-3 text-sm font-semibold border-b-2 transition-all @(_activeTab == "favourites" ? "border-rose-500 text-rose-600" : "border-transparent text-mystic-400 hover:text-rose-500")'>
<span class="inline-flex items-center gap-1.5">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
Favourites
@if (_favourites.Any())
{
<span class="ml-1 px-1.5 py-0.5 rounded-full bg-rose-500 text-white text-[10px]">@_favourites.Count</span>
}
</span>
</button>
</nav>
</div>
@if (_isLoading)
{
<div class="py-20 flex justify-center">
<div class="animate-spin rounded-full h-10 w-10 border-b-2 border-mystic-900"></div>
</div>
}
else
{
<div class="animate-in fade-in duration-300">
@if (_activeTab == "wishlist")
{
@if (!_wishlist.Any())
{
<div class="py-20 text-center bg-mystic-50 rounded-3xl border-2 border-dashed border-mystic-200">
<p class="text-mystic-400 font-medium">Your wishlist is empty. Add books that are currently out of stock!</p>
<a href="Books" class="inline-block mt-4 text-sm font-bold text-mystic-900 hover:underline underline-offset-4">Browse Catalog →</a>
</div>
}
else
{
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach (var book in _wishlist)
{
<div class="card p-4 flex gap-4 group hover:shadow-md transition-shadow relative">
<div class="w-20 h-28 bg-mystic-100 rounded-lg flex-shrink-0 flex items-center justify-center text-mystic-300 overflow-hidden border border-mystic-100">
@if (!string.IsNullOrWhiteSpace(book.CoverUrl))
{
<img src="@book.CoverUrl" alt="@book.Title" class="w-full h-full object-cover" />
}
else
{
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><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>
}
</div>
<div class="flex-grow min-w-0">
<h3 class="text-sm font-bold text-mystic-900 truncate">@book.Title</h3>
<p class="text-xs text-mystic-500 mt-0.5 truncate">@book.Author</p>
<div class="mt-2">
<span class="inline-flex items-center rounded-full @(book.AvailableCopies > 0 ? "bg-emerald-50 text-emerald-700" : "bg-rose-50 text-rose-700") px-2 py-0.5 text-[10px] font-bold">
@(book.AvailableCopies > 0 ? "Now Available" : "Out of Stock")
</span>
</div>
<div class="mt-4 flex items-center gap-2">
<button @onclick='() => Navigation.NavigateTo($"/Books")' class="text-[10px] font-bold text-mystic-600 hover:text-mystic-900 uppercase tracking-tight">View Info</button>
<span class="text-mystic-200">|</span>
<button @onclick="() => RemoveFromWishlist(book.Id)" class="text-[10px] font-bold text-rose-600 hover:text-rose-700 uppercase tracking-tight">Remove</button>
</div>
</div>
@if (book.AvailableCopies > 0)
{
<div class="absolute -top-2 -right-2 bg-emerald-500 text-white p-1 rounded-full shadow-sm animate-bounce">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path></svg>
</div>
}
</div>
}
</div>
<div class="mt-8 p-4 bg-mystic-900 rounded-2xl text-white flex items-center gap-4">
<div class="p-2 bg-mystic-800 rounded-lg">
<svg class="w-6 h-6 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
</div>
<p class="text-xs font-medium">We'll notify you via Firebase when out-of-stock items become available!</p>
</div>
}
}
else
{
@if (!_favourites.Any())
{
<div class="py-20 text-center bg-mystic-50 rounded-3xl border-2 border-dashed border-mystic-200">
<p class="text-mystic-400 font-medium">You haven't added any favourites yet.</p>
<a href="Books" class="inline-block mt-4 text-sm font-bold text-mystic-900 hover:underline underline-offset-4">Browse Catalog →</a>
</div>
}
else
{
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach (var book in _favourites)
{
<div class="card p-4 flex gap-4 group hover:shadow-md transition-shadow">
<div class="w-20 h-28 bg-mystic-100 rounded-lg flex-shrink-0 flex items-center justify-center text-mystic-300 overflow-hidden border border-mystic-100">
@if (!string.IsNullOrWhiteSpace(book.CoverUrl))
{
<img src="@book.CoverUrl" alt="@book.Title" class="w-full h-full object-cover" />
}
else
{
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><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>
}
</div>
<div class="flex-grow min-w-0">
<h3 class="text-sm font-bold text-mystic-900 truncate">@book.Title</h3>
<p class="text-xs text-mystic-500 mt-0.5 truncate">@book.Author</p>
<div class="mt-4 flex items-center gap-2">
<button @onclick='() => Navigation.NavigateTo($"/Books")' class="text-[10px] font-bold text-mystic-600 hover:text-mystic-900 uppercase tracking-tight">View Info</button>
<span class="text-mystic-200">|</span>
<button @onclick="() => RemoveFromFavourite(book.Id)" class="text-[10px] font-bold text-rose-600 hover:text-rose-700 uppercase tracking-tight">Remove</button>
</div>
</div>
</div>
}
</div>
}
}
</div>
}
</Authorized>
<NotAuthorized>
<div class="py-20 text-center">
<p class="text-mystic-500">Please log in as a member to view your wishlist.</p>
</div>
</NotAuthorized>
</AuthorizeView>
@code {
private string _activeTab = "wishlist";
private bool _isLoading = true;
private List<BookDto> _wishlist = new();
private List<BookDto> _favourites = new();
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await LoadDataAsync();
_isLoading = false;
StateHasChanged();
}
}
private async Task LoadDataAsync()
{
_wishlist = await WishlistService.GetWishlistAsync();
_favourites = await WishlistService.GetFavouritesAsync();
// Ensure server is synced with current local storage
await WishlistService.SyncCurrentWishlistAsync();
}
private async Task RemoveFromWishlist(int id)
{
await WishlistService.RemoveFromWishlistAsync(id);
await LoadDataAsync();
await JS.InvokeVoidAsync("showToast", "Removed from wishlist", "success");
}
private async Task RemoveFromFavourite(int id)
{
await WishlistService.RemoveFromFavouriteAsync(id);
await LoadDataAsync();
await JS.InvokeVoidAsync("showToast", "Removed from favourites", "success");
}
}
|