Yuyuqt
add: blazor web assembly.
4433399
Raw
History Blame Contribute Delete
12.3 kB
@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");
}
}