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
- <button 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
- <span class="absolute top-0 right-0 w-2 h-2 bg-primary rounded-full border-2 border-card"></span>
54
- </button>
 
 
 
 
 
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
+ }