Spaces:
Sleeping
Sleeping
| Frontend.Models.RewardsViewModel | |
| @{ | |
| ViewData["Title"] = "Loyalty Rewards"; | |
| } | |
| <div class="mb-10"> | |
| <div class="flex flex-col md:flex-row md:items-center justify-between gap-6"> | |
| <div> | |
| <h1 class="text-3xl font-extrabold tracking-tight text-slate-900 border-b border-slate-100 pb-4 mb-2">Loyalty Rewards</h1> | |
| <p class="text-slate-500 font-medium">Redeem your hard-earned points for exclusive library benefits.</p> | |
| </div> | |
| <div class="flex items-center gap-4 bg-amber-50 border border-amber-100 px-6 py-4 rounded-2xl shadow-sm"> | |
| <div class="p-2 bg-amber-100 rounded-lg text-amber-600"> | |
| <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="M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7"></path></svg> | |
| </div> | |
| <div> | |
| <p class="text-[10px] font-bold text-amber-600 uppercase tracking-widest mb-0.5">Your Balance</p> | |
| <p class="text-2xl font-black text-slate-900">@(Model.Account?.CurrentBalance.ToString("N0") ?? "0") Points</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> | |
| @if (!Model.Rewards.Any()) | |
| { | |
| <div class="col-span-full py-20 text-center bg-slate-50 rounded-3xl border-2 border-dashed border-slate-200"> | |
| <div class="text-slate-400 mb-4"> | |
| <svg class="w-16 h-16 mx-auto opacity-20" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path></svg> | |
| </div> | |
| <p class="text-slate-500 font-medium italic">No active rewards available at the moment. Check back soon!</p> | |
| </div> | |
| } | |
| @foreach (var reward in Model.Rewards) | |
| { | |
| <div class="group relative card p-6 hover:shadow-2xl hover:-translate-y-1 transition-all duration-300 border-t-4 border-amber-400"> | |
| <div class="flex justify-between items-start mb-6"> | |
| <div class="p-3 bg-amber-50 rounded-xl text-amber-600 group-hover:bg-amber-400 group-hover:text-white transition-colors"> | |
| <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="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"></path></svg> | |
| </div> | |
| <div class="text-right"> | |
| <span class="text-3xl font-black text-slate-900">@reward.PointCost.ToString("N0")</span> | |
| <span class="text-[10px] block font-bold text-slate-400 uppercase tracking-tighter">Points Required</span> | |
| </div> | |
| </div> | |
| <h3 class="text-lg font-bold text-slate-900 mb-2 truncate">@reward.Name</h3> | |
| <p class="text-sm text-slate-500 leading-relaxed mb-6 h-12 overflow-hidden line-clamp-2">@reward.Description</p> | |
| <div class="flex items-center justify-between pt-6 border-t border-slate-50"> | |
| <div class="flex flex-col"> | |
| <span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Stock</span> | |
| <span class="text-sm font-bold text-slate-700">@reward.StockQuantity available</span> | |
| </div> | |
| <button onclick="redeemReward('@reward.Id', '@reward.Name', @reward.PointCost)" | |
| class="bg-slate-900 text-white px-5 py-2.5 rounded-xl text-xs font-bold hover:bg-slate-800 hover:shadow-lg transition-all active:scale-95"> | |
| Redeem Reward | |
| </button> | |
| </div> | |
| @if (reward.StockQuantity < 10) | |
| { | |
| <div class="absolute top-2 right-2 flex"> | |
| <span class="animate-pulse bg-rose-500 text-white text-[8px] font-black px-1.5 py-0.5 rounded uppercase">Limited</span> | |
| </div> | |
| } | |
| </div> | |
| } | |
| </div> | |
| <div class="mt-12 p-8 bg-slate-900 rounded-3xl text-white relative overflow-hidden"> | |
| <div class="relative z-10 flex flex-col md:flex-row items-center justify-between gap-8"> | |
| <div class="text-center md:text-left"> | |
| <h2 class="text-2xl font-bold mb-2">How to earn more points?</h2> | |
| <p class="text-slate-400 max-w-md">Borrow books, subscribe to memberships, and return books on time to stack up your points and unlock even bigger rewards!</p> | |
| </div> | |
| <a asp-controller="Books" asp-action="Index" class="bg-amber-400 text-slate-900 px-8 py-4 rounded-2xl font-black hover:bg-amber-300 transition-all shadow-xl shadow-amber-400/20 whitespace-nowrap"> | |
| Browse Books Now | |
| </a> | |
| </div> | |
| <!-- Decorative background element --> | |
| <div class="absolute -right-10 -bottom-10 w-64 h-64 bg-amber-400/10 rounded-full blur-3xl"></div> | |
| <div class="absolute -left-10 -top-10 w-48 h-48 bg-blue-400/5 rounded-full blur-3xl"></div> | |
| </div> | |
| <script> | |
| async function redeemReward(rewardId, rewardName, cost) { | |
| if (!confirm(`Are you sure you want to redeem ${rewardName} for ${cost.toLocaleString()} points?`)) { | |
| return; | |
| } | |
| try { | |
| const response = await fetch('/Borrowings/Redeem', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| }, | |
| body: new URLSearchParams({ rewardId: rewardId }) | |
| }); | |
| const result = await response.json(); | |
| if (result.success) { | |
| alert(result.message); | |
| window.location.reload(); | |
| } else { | |
| alert('Error: ' + result.message); | |
| } | |
| } catch (error) { | |
| console.error('Redemption error:', error); | |
| alert('An unexpected error occurred. Please try again.'); | |
| } | |
| } | |
| </script> | |