File size: 14,873 Bytes
cf599e0
0acba57
 
 
cf599e0
 
 
 
6aad87c
cf599e0
6aad87c
cf599e0
 
 
 
 
 
 
 
 
 
 
 
 
 
0acba57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1fe511c
 
 
 
0acba57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf9e5bb
 
 
 
0acba57
 
cf599e0
 
6aad87c
0acba57
 
 
6aad87c
0acba57
 
 
 
6aad87c
0acba57
 
 
 
 
6aad87c
0acba57
 
 
 
6aad87c
0acba57
 
 
 
cf599e0
 
 
 
 
 
 
6aad87c
cf599e0
 
 
 
 
 
6aad87c
cf599e0
 
 
6aad87c
cf599e0
 
 
 
ccc2569
cf599e0
 
 
 
ccc2569
 
 
 
 
 
 
0acba57
 
 
 
 
 
 
 
cf599e0
 
 
 
 
 
 
0acba57
 
 
 
 
 
 
6aad87c
0acba57
 
cf599e0
 
 
 
 
 
 
 
 
 
0acba57
ccc2569
 
 
0acba57
 
ccc2569
0acba57
 
 
 
 
 
 
 
 
ccc2569
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0acba57
 
 
 
 
 
 
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
@inherits LayoutComponentBase
@using BlazorFrontend.Providers
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthStateProvider

<div class="flex h-full overflow-hidden">
    <!-- Sidebar -->
    <aside class="hidden md:flex md:w-64 md:flex-col md:fixed md:inset-y-0 z-50">
        <div class="flex flex-col flex-grow border-r border-mystic-200 bg-white pt-5 pb-4 overflow-y-auto">
            <div class="flex items-center flex-shrink-0 px-4 gap-2">
                <div class="bg-mystic-900 rounded-lg p-1.5 text-white">
                    <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 6 4 14"/><path d="M12 6v14"/><path d="M8 8v12"/><path d="M4 4v16"/><path d="M4 12V4l4 4"/><path d="m12 6 4 4"/></svg>
                </div>
                <span class="text-lg font-bold tracking-tight">Library OS</span>
            </div>
            <div class="mt-8 flex-grow flex flex-col">
                <nav class="flex-1 px-2 space-y-1">
                    <NavLink href="" Match="NavLinkMatch.All" class="nav-link" ActiveClass="active">
                        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="15" rx="1"/></svg>
                        Dashboard
                    </NavLink>
                    <NavLink href="Books" class="nav-link" ActiveClass="active">
                        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><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>
                        Books Catalog
                    </NavLink>

                    <AuthorizeView>
                        <NotAuthorized>
                            <NavLink href="Borrowings" class="nav-link" ActiveClass="active">
                                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="m21 21-6-6m6 6v-4.8m0 4.8h-4.8"/><path d="M3 16.2V21m0 0h4.8M3 21l6-6"/><path d="M21 7.8V3m0 0h-4.8M21 3l-6 6"/><path d="M3 7.8V3m0 0h4.8M3 3l6 6"/></svg>
                                Active Loans
                            </NavLink>
                        </NotAuthorized>
                    </AuthorizeView>

                    <AuthorizeView>
                        <Authorized Context="authContext">
                            @if (!authContext.User.IsInRole("Librarian"))
                            {
                                <NavLink href="Borrowings" class="nav-link" ActiveClass="active">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="m21 21-6-6m6 6v-4.8m0 4.8h-4.8"/><path d="M3 16.2V21m0 0h4.8M3 21l6-6"/><path d="M21 7.8V3m0 0h-4.8M21 3l-6 6"/><path d="M3 7.8V3m0 0h4.8M3 3l6 6"/></svg>
                                    Active Loans
                                </NavLink>
                                <NavLink href="Membership" class="nav-link" ActiveClass="active">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/></svg>
                                    My Membership
                                </NavLink>
                                <NavLink href="Rewards" class="nav-link" ActiveClass="active">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M20 7h-9"/><path d="M14 17H5"/><circle cx="17" cy="17" r="3"/><circle cx="7" cy="7" r="3"/></svg>
                                    Rewards
                                </NavLink>
                                <NavLink href="Wishlist" class="nav-link" ActiveClass="active">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" /></svg>
                                    Wishlist
                                </NavLink>
                            }
                        </Authorized>
                    </AuthorizeView>

                    <AuthorizeView Roles="Librarian">
                        <Authorized>
                            <NavLink href="Members" class="nav-link" ActiveClass="active">
                                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
                                Members
                            </NavLink>
                            <NavLink href="Borrowings/Manage" class="nav-link" ActiveClass="active">
                                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M11 15h2a2 2 0 1 0 0-4h-2a2 2 0 1 1 0-4h2"/><path d="M12 5v14"/><path d="M18 19V5l-4 4"/><circle cx="12" cy="12" r="10"/></svg>
                                Manage Borrowings
                            </NavLink>
                            <NavLink href="Rewards/Manage" class="nav-link" ActiveClass="active">
                                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
                                Manage Rewards
                            </NavLink>
                            <NavLink href="Insights" class="nav-link" ActiveClass="active">
                                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="flex-shrink-0 mr-3"><path d="M21 12V7H5a2 2 0 010-4h14v4"/><path d="M3 5v14a2 2 0 002 2h16v-5"/><path d="M18 12a2 2 0 000 4h4v-4Z"/></svg>
                                User Insights
                            </NavLink>
                        </Authorized>
                    </AuthorizeView>
                </nav>
            </div>
            <div class="px-4 py-4 border-t border-mystic-100">
                <AuthorizeView>
                    <Authorized Context="authContext">
                        <div class="flex items-center gap-3">
                            <div class="h-8 w-8 rounded-full bg-mystic-900 flex items-center justify-center text-white font-bold text-xs uppercase">
                                @(authContext.User.Identity?.Name?.Substring(0, 2) ?? "US")
                            </div>
                            <div class="flex flex-col">
                                <span class="text-sm font-medium">@authContext.User.Identity?.Name</span>
                                <span class="text-xs text-mystic-500">@authContext.User.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Role || c.Type == "role")?.Value</span>
                            </div>
                        </div>
                    </Authorized>
                    <NotAuthorized>
                        <div class="flex items-center gap-3">
                            <div class="h-8 w-8 rounded-full bg-mystic-900 flex items-center justify-center text-white font-bold text-xs uppercase">
                                GU
                            </div>
                            <div class="flex flex-col">
                                <span class="text-sm font-medium">Guest User</span>
                                <span class="text-xs text-mystic-500">Sign in to borrow</span>
                            </div>
                        </div>
                    </NotAuthorized>
                </AuthorizeView>
            </div>
        </div>
    </aside>

    <!-- Main Content Wrapper -->
    <div class="md:pl-64 flex flex-col flex-1 w-0">
        <!-- Topbar -->
        <header class="flex-shrink-0 flex h-16 bg-white border-b border-mystic-200">
            <div class="flex-1 px-4 flex justify-between">
                <div class="flex-1 flex items-center">
                    <div class="w-full max-w-sm">
                        <label for="search" class="sr-only">Search</label>
                        <div class="relative">
                            <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                                <svg class="h-4 w-4 text-mystic-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
                                    <path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
                                </svg>
                            </div>
                            <input id="search" name="search" class="block w-full pl-10 pr-3 py-2 border border-mystic-200 rounded-md leading-5 bg-mystic-50 placeholder-mystic-400 focus:outline-none focus:ring-1 focus:ring-mystic-900 focus:border-mystic-900 sm:text-sm transition-all" placeholder="Search books, authors, ISBN..." type="search">
                        </div>
                    </div>
                </div>
                <div class="ml-4 flex items-center md:ml-6 gap-4">
                    <a href="notifications" class="relative bg-white p-1 rounded-full text-mystic-400 hover:text-mystic-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-mystic-900">
                        <span class="sr-only">View notifications</span>
                        <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                            <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" />
                        </svg>
                        @if (_unreadCount > 0)
                        {
                            <span class="absolute top-0 right-0 block h-4 w-4 rounded-full bg-rose-500 text-[10px] font-bold text-white flex items-center justify-center border-2 border-white transform translate-x-1/2 -translate-y-1/2">
                                @(_unreadCount > 9 ? "9+" : _unreadCount)
                            </span>
                        }
                    </a>
                    <AuthorizeView>
                        <Authorized>
                            <a href="/logout" class="btn-secondary py-1.5">Logout</a>
                        </Authorized>
                        <NotAuthorized>
                            <a href="/login" class="btn-primary py-1.5">Sign In</a>
                        </NotAuthorized>
                    </AuthorizeView>
                </div>
            </div>
        </header>

        <!-- Main Content -->
        <main class="flex-1 relative overflow-y-auto focus:outline-none">
            <div class="py-8 px-4 sm:px-6 md:px-8 max-w-7xl mx-auto">
                @if (_isReady)
                {
                    @Body
                }
                else
                {
                    <div class="flex items-center justify-center h-64">
                        <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-mystic-900"></div>
                    </div>
                }
            </div>
        </main>
    </div>
</div>

<div id="blazor-error-ui" class="hidden">
    An unhandled error has occurred.
    <a href="" class="reload text-blue-500 hover:underline">Reload</a>
    <a class="dismiss cursor-pointer">🗙</a>
</div>

@inject BlazorFrontend.Services.LibraryApiClient ApiClient
@inject IJSRuntime JS

@code {
    private bool _isReady;
    private int _unreadCount;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var jwtProvider = AuthStateProvider as JwtAuthenticationStateProvider;
            if (jwtProvider != null)
            {
                await jwtProvider.LoadTokenAsync();
                
                // Fetch unread count if authenticated
                var authState = await AuthStateProvider.GetAuthenticationStateAsync();
                if (authState.User.Identity?.IsAuthenticated == true)
                {
                    _unreadCount = await ApiClient.GetUnreadNotificationCountAsync();
                    
                    // Update FCM Token
                    try
                    {
                        var token = await JS.InvokeAsync<string>("window.getFcmToken");
                        if (!string.IsNullOrEmpty(token))
                        {
                            await ApiClient.UpdateFcmTokenAsync(token);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Error updating FCM token: {ex.Message}");
                    }
                }
            }
            
            _isReady = true;
            StateHasChanged();
        }
    }
}