File size: 15,598 Bytes
6f32ae8
313d29f
6f32ae8
 
 
d8a7fc4
 
313d29f
6f32ae8
d3d3b46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6f32ae8
 
d3d3b46
6f32ae8
 
 
ec76476
6f32ae8
 
d3d3b46
6f32ae8
d3d3b46
4433399
d3d3b46
 
 
 
 
 
 
 
 
313d29f
6f32ae8
313d29f
9edb017
313d29f
 
9edb017
 
 
6f32ae8
d3d3b46
 
 
d8a7fc4
d3d3b46
 
 
6f32ae8
d3d3b46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d8a7fc4
 
d3d3b46
 
 
 
 
 
 
 
 
 
 
 
 
 
6f32ae8
d3d3b46
d8a7fc4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d3d3b46
 
d8a7fc4
 
 
 
 
 
d3d3b46
 
 
d8a7fc4
d3d3b46
 
 
6f32ae8
 
 
 
 
313d29f
 
 
6f32ae8
d8a7fc4
313d29f
d8a7fc4
313d29f
 
 
 
 
 
d8a7fc4
 
313d29f
d8a7fc4
313d29f
 
 
 
 
 
d8a7fc4
313d29f
d8a7fc4
 
 
313d29f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d8a7fc4
6f32ae8
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
@inherits LayoutComponentBase
@implements IDisposable
@using BlazorWebAssembly.Services
@inject ThemeService ThemeService
@inject AuthenticationStateProvider AuthStateProvider
@inject IJSRuntime JS
@inject LibraryApiClient ApiClient
@inject NotificationStateService NotifService

<AuthorizeView>
    <Authorized>
        <div class="flex h-screen overflow-hidden bg-body text-main animate-fade font-sans">
            <!-- Sidebar (Dashboard View) -->
            <aside class="hidden lg:flex lg:w-64 lg:flex-col lg:fixed lg:inset-y-0 z-50 border-r border-border bg-card">
                <div class="flex flex-col flex-grow pt-8 pb-4 overflow-y-auto">
                    <div class="flex items-center flex-shrink-0 px-6 gap-3 mb-10">
                        <div class="bg-primary rounded-xl p-2 text-white shadow-lg shadow-primary/20">
                            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" 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-xl font-black tracking-tighter uppercase">LibraryLuxe</span>
                    </div>
                    <div class="flex-grow px-3">
                        <NavMenu />
                    </div>
                    <div class="px-6 py-6 border-t border-border">
                        <div class="mb-6">
                            <p class="text-[10px] font-black text-muted uppercase tracking-[0.2em] mb-4 text-center">Switch Theme</p>
                            <ThemeSwitcher />
                        </div>
                        <div class="flex items-center gap-3 p-3 bg-body rounded-2xl border border-border">
                            <div class="h-10 w-10 rounded-xl bg-primary flex items-center justify-center text-white font-bold shadow-sm">
                                @(context.User.Identity?.Name?.Length > 0 ? context.User.Identity.Name[..1].ToUpper() : "?")
                            </div>
                            <div class="flex flex-col min-w-0">
                                <span class="text-xs font-bold truncate">@context.User.Identity?.Name</span>
                                <span class="text-[10px] text-muted font-medium uppercase tracking-wider">@(context.User.IsInRole("Librarian") ? "Librarian" : "Member")</span>
                            </div>
                        </div>
                    </div>
                </div>
            </aside>

            <div class="lg:pl-64 flex flex-col flex-1 w-0">
                <header class="h-20 bg-card/80 backdrop-blur-md border-b border-border sticky top-0 z-40 flex items-center justify-between px-8">
                    <div class="flex-grow max-w-lg">
                        <div class="relative">
                            <svg class="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
                            <input type="text" placeholder="Quick search..." class="w-full bg-body border border-border rounded-xl pl-12 pr-4 py-2.5 text-sm focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all shadow-sm" />
                        </div>
                    </div>
                    <div class="flex items-center gap-6">
                        <NavLink href="/notifications" class="relative text-muted hover:text-primary transition-colors p-2 rounded-xl hover:bg-primary/5">
                            <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>
                            @if (NotifService.UnreadCount > 0)
                            {
                                <span class="absolute -top-0.5 -right-0.5 flex h-5 w-5 items-center justify-center bg-primary text-white text-[9px] font-black rounded-full border-2 border-card shadow-md animate-bounce z-10">
                                    @(NotifService.UnreadCount > 9 ? "9+" : NotifService.UnreadCount)
                                </span>
                            }
                        </NavLink>
                        <a href="/logout" class="text-xs font-black uppercase tracking-widest text-muted hover:text-red-500 transition-colors">Logout</a>
                    </div>
                </header>
                <main class="flex-1 overflow-y-auto p-8 scrollbar-hide">
                    <div class="max-w-7xl mx-auto w-full">
                        @Body
                    </div>
                </main>
            </div>
        </div>
    </Authorized>
    <NotAuthorized>
        <div class="min-h-screen bg-body text-main animate-fade font-sans flex flex-col">
            <!-- Top Navbar (Guest View) -->
            <nav class="h-20 bg-card/80 backdrop-blur-md border-b border-border sticky top-0 z-50 px-8 flex items-center justify-between shadow-sm">
                <div class="flex items-center gap-10">
                    <a href="/" class="flex items-center gap-3 group">
                        <div class="bg-primary rounded-xl p-2 text-white shadow-lg shadow-primary/20 group-hover:scale-110 transition-transform">
                            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" 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-xl font-black tracking-tighter uppercase">LibraryLuxe</span>
                    </a>
                    <div class="hidden md:flex items-center gap-8">
                        <NavLink href="/" Match="NavLinkMatch.All" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">Home</NavLink>
                        <NavLink href="/membership" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">Membership</NavLink>
                        <NavLink href="/features" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">Features</NavLink>
                        <NavLink href="/about" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">About Us</NavLink>
                        <NavLink href="/contact" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">Contact</NavLink>
                    </div>
                </div>
                <div class="flex items-center gap-6">
                    <ThemeSwitcher />
                    <div class="h-6 w-px bg-border hidden sm:block"></div>
                    <div class="flex items-center gap-4">
                        <a href="/login" class="text-xs font-black uppercase tracking-widest text-muted hover:text-primary transition-colors">Sign In</a>
                        <a href="/register" class="bg-primary text-white px-6 py-2.5 rounded-xl text-xs font-black uppercase tracking-widest hover:bg-primary-hover shadow-lg shadow-primary/20 transition-all transform hover:scale-105">Get Started</a>
                    </div>
                </div>
            </nav>
            <main class="flex-grow">
                @Body
            </main>
            <footer class="py-20 border-t border-border bg-card">
                <div class="max-w-7xl mx-auto px-8">
                    <div class="grid grid-cols-1 md:grid-cols-4 gap-12 mb-16">
                        <div class="col-span-1 md:col-span-2">
                            <div class="flex items-center gap-3 mb-6">
                                <div class="bg-primary rounded-xl p-2 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.5" 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-xl font-black tracking-tighter uppercase">LibraryLuxe</span>
                            </div>
                            <p class="text-muted text-sm max-w-sm leading-relaxed mb-8">
                                Redefining the modern library experience with digital-first solutions and community-driven rewards.
                            </p>
                            <div class="flex gap-4">
                                <!-- Social Icons Placeholder -->
                                <div class="w-10 h-10 rounded-xl bg-body border border-border flex items-center justify-center text-muted hover:text-primary transition-colors cursor-pointer">
                                    <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"/></svg>
                                </div>
                                <div class="w-10 h-10 rounded-xl bg-body border border-border flex items-center justify-center text-muted hover:text-primary transition-colors cursor-pointer">
                                    <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/></svg>
                                </div>
                            </div>
                        </div>
                        <div>
                            <h4 class="text-xs font-black uppercase tracking-[0.2em] text-main mb-6">Navigation</h4>
                            <ul class="space-y-4">
                                <li><a href="/" class="text-sm text-muted hover:text-primary transition-colors">Home</a></li>
                                <li><a href="/membership" class="text-sm text-muted hover:text-primary transition-colors">Membership</a></li>
                                <li><a href="/features" class="text-sm text-muted hover:text-primary transition-colors">Features</a></li>
                                <li><a href="/faq" class="text-sm text-muted hover:text-primary transition-colors">FAQ</a></li>
                            </ul>
                        </div>
                        <div>
                            <h4 class="text-xs font-black uppercase tracking-[0.2em] text-main mb-6">Legal</h4>
                            <ul class="space-y-4">
                                <li><a href="/privacy" class="text-sm text-muted hover:text-primary transition-colors">Privacy Policy</a></li>
                                <li><a href="/terms" class="text-sm text-muted hover:text-primary transition-colors">Terms of Service</a></li>
                                <li><a href="/contact" class="text-sm text-muted hover:text-primary transition-colors">Contact Support</a></li>
                            </ul>
                        </div>
                    </div>
                    <div class="pt-8 border-t border-border flex flex-col md:row justify-between items-center gap-4">
                        <p class="text-[10px] font-bold text-muted uppercase tracking-[0.2em]">&copy; 2026 LibraryLuxe Ecosystem. Built with passion for readers.</p>
                        <div class="flex items-center gap-2">
                            <div class="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div>
                            <span class="text-[10px] font-black uppercase tracking-widest text-muted">System Operational</span>
                        </div>
                    </div>
                </div>
            </footer>

        </div>
    </NotAuthorized>
</AuthorizeView>

@code {
    protected override async Task OnInitializedAsync()
    {
        await ThemeService.InitializeAsync();
        AuthStateProvider.AuthenticationStateChanged += OnAuthStateChanged;
        NotifService.OnChange += HandleNotifChange;
        _ = UpdateNotificationCount();
    }

    private async void OnAuthStateChanged(Task<AuthenticationState> task)
    {
        await InvokeAsync(UpdateNotificationCount);
    }

    private async Task UpdateNotificationCount()
    {
        try
        {
            var authState = await AuthStateProvider.GetAuthenticationStateAsync();
            if (authState.User.Identity?.IsAuthenticated == true)
            {
                var count = await ApiClient.GetUnreadNotificationCountAsync();
                NotifService.SetUnreadCount(count);
                
                // Also update FCM token if not already done
                var token = await JS.InvokeAsync<string>("getFcmToken");
                if (!string.IsNullOrEmpty(token))
                {
                    await ApiClient.UpdateFcmTokenAsync(token);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[MainLayout] Error updating notifications: {ex.Message}");
        }
    }

    protected override void OnAfterRender(bool firstRender)
    {
        // No longer using OnAfterRender for initial fetch as it's handled in OnInitialized and events
    }

    private async void HandleNotifChange() => await InvokeAsync(StateHasChanged);

    public void Dispose()
    {
        AuthStateProvider.AuthenticationStateChanged -= OnAuthStateChanged;
        NotifService.OnChange -= HandleNotifChange;
    }
}