feat(ui): Hub Navigation, Profile Dashboard and Auth Stability Fixes (#31)

This PR implements the Hub Navigation system and the Profile Dashboard, while resolving critical session synchronization issues.

### Key Changes
- **Hub Navigation**: Introduced `MainHubLayout` with a premium glassmorphism sidebar, providing access to Dashboard, Library, Concepts Map, and Profile.
- **Profile Dashboard**: Implemented a high-fidelity Profile page (#27) with learning metrics, AI token usage tracking, and system rank visualization.
- **Stability Fixes**:
    - Resolved an infinite network loop on the `/profile` page by implementing request deduplication and in-memory caching in `IdentityService`.
    - Added environment-aware guards to prevent illegal JavaScript interop calls during server-side prerendering.
    - Implemented automatic session invalidation on `401 Unauthorized` responses to handle stale authentication states gracefully.
- **Reader Integration**: Added a "Return to Dashboard" option in the reader toolbar (#26).

Closes #26
Closes #27

Reviewed-on: #31
Co-authored-by: Marek Jasiński <jasins.marek@gmail.com>
Co-committed-by: Marek Jasiński <jasins.marek@gmail.com>
This commit was merged in pull request #31.
This commit is contained in:
2026-05-10 17:36:35 +00:00
committed by Marek Jaisński
parent 34794db209
commit 2e23a032d3
56 changed files with 4292 additions and 481 deletions
@@ -46,6 +46,7 @@ public class SyncService : ISyncService, IAsyncDisposable
_hubConnection.On<string, DateTime>("ProgressUpdated", async (pageId, timestamp) =>
{
// Note: In the future we might want to receive ebookId and progress here too
if (OnProgressReceived != null) await OnProgressReceived(pageId, timestamp);
});
@@ -63,7 +64,7 @@ public class SyncService : ISyncService, IAsyncDisposable
private string? _lastSentPageId;
public async Task<Result> UpdateProgressAsync(string pageId)
public async Task<Result> UpdateProgressAsync(string pageId, Guid ebookId, double progress, string? chapterTitle, int chapterIndex)
{
if (pageId == _lastSentPageId) return Result.Ok();
@@ -82,7 +83,7 @@ public class SyncService : ISyncService, IAsyncDisposable
if (_hubConnection?.State == HubConnectionState.Connected)
{
await _hubConnection.SendAsync("UpdateProgress", pageId, token);
await _hubConnection.SendAsync("UpdateProgress", pageId, ebookId, progress, chapterTitle, token);
_lastSentPageId = pageId;
}
}