Spaces:
Sleeping
Sleeping
Yuyuqt commited on
Commit ·
9edb017
1
Parent(s): d8a7fc4
add: firebase noti
Browse files
BlazorWebAssembly/Layout/MainLayout.razor
CHANGED
|
@@ -48,10 +48,15 @@
|
|
| 48 |
</div>
|
| 49 |
</div>
|
| 50 |
<div class="flex items-center gap-6">
|
| 51 |
-
<
|
| 52 |
<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>
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
<a href="/logout" class="text-xs font-black uppercase tracking-widest text-muted hover:text-red-500 transition-colors">Logout</a>
|
| 56 |
</div>
|
| 57 |
</header>
|
|
@@ -150,6 +155,8 @@
|
|
| 150 |
</AuthorizeView>
|
| 151 |
|
| 152 |
@code {
|
|
|
|
|
|
|
| 153 |
protected override async Task OnInitializedAsync()
|
| 154 |
{
|
| 155 |
await ThemeService.InitializeAsync();
|
|
@@ -166,6 +173,9 @@
|
|
| 166 |
{
|
| 167 |
try
|
| 168 |
{
|
|
|
|
|
|
|
|
|
|
| 169 |
var token = await JS.InvokeAsync<string>("getFcmToken");
|
| 170 |
if (!string.IsNullOrEmpty(token))
|
| 171 |
{
|
|
|
|
| 48 |
</div>
|
| 49 |
</div>
|
| 50 |
<div class="flex items-center gap-6">
|
| 51 |
+
<NavLink href="/notifications" class="relative text-muted hover:text-primary transition-colors">
|
| 52 |
<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>
|
| 53 |
+
@if (_unreadCount > 0)
|
| 54 |
+
{
|
| 55 |
+
<span class="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center bg-primary text-white text-[8px] font-black rounded-full border-2 border-card animate-bounce">
|
| 56 |
+
@(_unreadCount > 9 ? "9+" : _unreadCount)
|
| 57 |
+
</span>
|
| 58 |
+
}
|
| 59 |
+
</NavLink>
|
| 60 |
<a href="/logout" class="text-xs font-black uppercase tracking-widest text-muted hover:text-red-500 transition-colors">Logout</a>
|
| 61 |
</div>
|
| 62 |
</header>
|
|
|
|
| 155 |
</AuthorizeView>
|
| 156 |
|
| 157 |
@code {
|
| 158 |
+
private int _unreadCount = 0;
|
| 159 |
+
|
| 160 |
protected override async Task OnInitializedAsync()
|
| 161 |
{
|
| 162 |
await ThemeService.InitializeAsync();
|
|
|
|
| 173 |
{
|
| 174 |
try
|
| 175 |
{
|
| 176 |
+
_unreadCount = await ApiClient.GetUnreadNotificationCountAsync();
|
| 177 |
+
StateHasChanged();
|
| 178 |
+
|
| 179 |
var token = await JS.InvokeAsync<string>("getFcmToken");
|
| 180 |
if (!string.IsNullOrEmpty(token))
|
| 181 |
{
|
BlazorWebAssembly/Layout/NavMenu.razor
CHANGED
|
@@ -9,6 +9,11 @@
|
|
| 9 |
Books Catalog
|
| 10 |
</NavLink>
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
<AuthorizeView Roles="Member">
|
| 13 |
<NavLink class="nav-link-v" href="/borrowings">
|
| 14 |
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path></svg>
|
|
|
|
| 9 |
Books Catalog
|
| 10 |
</NavLink>
|
| 11 |
|
| 12 |
+
<NavLink class="nav-link-v" href="/notifications">
|
| 13 |
+
<svg class="w-5 h-5 mr-3" 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>
|
| 14 |
+
Notifications
|
| 15 |
+
</NavLink>
|
| 16 |
+
|
| 17 |
<AuthorizeView Roles="Member">
|
| 18 |
<NavLink class="nav-link-v" href="/borrowings">
|
| 19 |
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path></svg>
|
BlazorWebAssembly/Pages/Notifications.razor
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@page "/notifications"
|
| 2 |
+
@using LibraryManagement.Shared.Models
|
| 3 |
+
@using BlazorWebAssembly.Services
|
| 4 |
+
@inject LibraryApiClient ApiClient
|
| 5 |
+
@inject IJSRuntime JS
|
| 6 |
+
|
| 7 |
+
<PageTitle>Notifications | LibraryLuxe</PageTitle>
|
| 8 |
+
|
| 9 |
+
<AuthorizeView>
|
| 10 |
+
<Authorized>
|
| 11 |
+
<div class="max-w-4xl mx-auto py-12 px-6 animate-fade">
|
| 12 |
+
<!-- Header -->
|
| 13 |
+
<div class="flex items-center justify-between mb-12">
|
| 14 |
+
<div>
|
| 15 |
+
<h2 class="text-[10px] font-black text-primary uppercase tracking-[0.3em] mb-2">User Updates</h2>
|
| 16 |
+
<h1 class="text-4xl font-black tracking-tighter text-main uppercase italic">Notifications</h1>
|
| 17 |
+
</div>
|
| 18 |
+
@if (_notifications.Any(n => !n.IsRead))
|
| 19 |
+
{
|
| 20 |
+
<button @onclick="MarkAllAsRead"
|
| 21 |
+
class="text-[10px] font-black uppercase tracking-widest px-6 py-3 bg-primary/10 text-primary rounded-xl hover:bg-primary hover:text-white transition-all transform hover:scale-105 active:scale-95 shadow-sm">
|
| 22 |
+
Mark All As Read
|
| 23 |
+
</button>
|
| 24 |
+
}
|
| 25 |
+
</div>
|
| 26 |
+
|
| 27 |
+
@if (_isLoading)
|
| 28 |
+
{
|
| 29 |
+
<div class="space-y-4">
|
| 30 |
+
@for (int i = 0; i < 4; i++)
|
| 31 |
+
{
|
| 32 |
+
<div class="h-24 bg-card rounded-2xl border border-border animate-pulse"></div>
|
| 33 |
+
}
|
| 34 |
+
</div>
|
| 35 |
+
}
|
| 36 |
+
else if (!_notifications.Any())
|
| 37 |
+
{
|
| 38 |
+
<div class="bg-card rounded-[2.5rem] p-20 text-center border border-border shadow-sm">
|
| 39 |
+
<div class="w-20 h-20 bg-body rounded-3xl flex items-center justify-center mx-auto mb-6 text-muted border border-border">
|
| 40 |
+
<svg class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 41 |
+
<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" />
|
| 42 |
+
</svg>
|
| 43 |
+
</div>
|
| 44 |
+
<h3 class="text-xl font-black text-main uppercase tracking-tight mb-2">Clean Slate</h3>
|
| 45 |
+
<p class="text-muted text-sm font-medium">You're all caught up. New updates will appear here.</p>
|
| 46 |
+
</div>
|
| 47 |
+
}
|
| 48 |
+
else
|
| 49 |
+
{
|
| 50 |
+
<div class="grid gap-4">
|
| 51 |
+
@foreach (var notif in _notifications)
|
| 52 |
+
{
|
| 53 |
+
<div class="group bg-card rounded-2xl p-6 border border-border hover:border-primary/30 transition-all duration-300 shadow-sm relative overflow-hidden @(!notif.IsRead ? "bg-primary/[0.02]" : "")">
|
| 54 |
+
@if (!notif.IsRead)
|
| 55 |
+
{
|
| 56 |
+
<div class="absolute left-0 top-0 bottom-0 w-1.5 bg-primary"></div>
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
<div class="flex gap-6">
|
| 60 |
+
<div class="flex-shrink-0">
|
| 61 |
+
<div class='w-12 h-12 rounded-2xl flex items-center justify-center @GetIconClass(notif.Type) shadow-sm'>
|
| 62 |
+
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 63 |
+
@if (notif.Type == "Warning") { <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> }
|
| 64 |
+
else if (notif.Type == "Info") { <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> }
|
| 65 |
+
else { <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" 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" /> }
|
| 66 |
+
</svg>
|
| 67 |
+
</div>
|
| 68 |
+
</div>
|
| 69 |
+
|
| 70 |
+
<div class="flex-1 min-w-0">
|
| 71 |
+
<div class="flex items-start justify-between gap-4 mb-2">
|
| 72 |
+
<h4 class="text-sm font-black text-main uppercase tracking-tight @(!notif.IsRead ? "text-primary" : "")">@notif.Title</h4>
|
| 73 |
+
<span class="text-[10px] font-black text-muted uppercase tracking-widest whitespace-nowrap">@notif.CreatedAt.ToString("MMM dd, HH:mm")</span>
|
| 74 |
+
</div>
|
| 75 |
+
<p class="text-sm text-muted font-medium leading-relaxed mb-4">@notif.Message</p>
|
| 76 |
+
|
| 77 |
+
@if (!string.IsNullOrEmpty(notif.ActionLink))
|
| 78 |
+
{
|
| 79 |
+
<a href="@notif.ActionLink" class="inline-flex items-center gap-2 text-[10px] font-black text-primary uppercase tracking-[0.2em] hover:gap-4 transition-all">
|
| 80 |
+
@notif.ActionText
|
| 81 |
+
<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="M14 5l7 7m0 0l-7 7m7-7H3"/></svg>
|
| 82 |
+
</a>
|
| 83 |
+
}
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
}
|
| 88 |
+
</div>
|
| 89 |
+
}
|
| 90 |
+
</div>
|
| 91 |
+
</Authorized>
|
| 92 |
+
<NotAuthorized>
|
| 93 |
+
<div class="min-h-[60vh] flex flex-col items-center justify-center px-6 text-center">
|
| 94 |
+
<h2 class="text-4xl font-black text-main uppercase tracking-tighter mb-6">Identity Required</h2>
|
| 95 |
+
<p class="text-muted max-w-md mb-10 font-medium leading-relaxed">Please sign in to your LibraryLuxe account to access your personal updates and notifications.</p>
|
| 96 |
+
<a href="/login" class="bg-primary text-white px-10 py-4 rounded-2xl text-[10px] font-black uppercase tracking-[0.3em] hover:bg-primary-hover shadow-xl shadow-primary/20 transition-all transform hover:scale-105 active:scale-95">Sign In Now</a>
|
| 97 |
+
</div>
|
| 98 |
+
</NotAuthorized>
|
| 99 |
+
</AuthorizeView>
|
| 100 |
+
|
| 101 |
+
@code {
|
| 102 |
+
private List<NotificationDto> _notifications = new();
|
| 103 |
+
private bool _isLoading = true;
|
| 104 |
+
|
| 105 |
+
protected override async Task OnInitializedAsync()
|
| 106 |
+
{
|
| 107 |
+
await LoadNotifications();
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
private async Task LoadNotifications()
|
| 111 |
+
{
|
| 112 |
+
try
|
| 113 |
+
{
|
| 114 |
+
_isLoading = true;
|
| 115 |
+
_notifications = (await ApiClient.GetNotificationsAsync()).ToList();
|
| 116 |
+
}
|
| 117 |
+
catch (Exception ex)
|
| 118 |
+
{
|
| 119 |
+
Console.WriteLine($"Error: {ex.Message}");
|
| 120 |
+
}
|
| 121 |
+
finally
|
| 122 |
+
{
|
| 123 |
+
_isLoading = false;
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
private async Task MarkAllAsRead()
|
| 128 |
+
{
|
| 129 |
+
try
|
| 130 |
+
{
|
| 131 |
+
var success = await ApiClient.MarkNotificationsReadAsync();
|
| 132 |
+
if (success) await LoadNotifications();
|
| 133 |
+
}
|
| 134 |
+
catch (Exception ex)
|
| 135 |
+
{
|
| 136 |
+
Console.WriteLine($"Error: {ex.Message}");
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
private string GetIconClass(string type) => type switch
|
| 141 |
+
{
|
| 142 |
+
"Warning" => "bg-orange-500/10 text-orange-500",
|
| 143 |
+
"Info" => "bg-blue-500/10 text-blue-500",
|
| 144 |
+
_ => "bg-primary/10 text-primary"
|
| 145 |
+
};
|
| 146 |
+
}
|