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:
@@ -0,0 +1,116 @@
|
||||
@inherits LayoutComponentBase
|
||||
@using NexusReader.Application.Abstractions.Services
|
||||
@using NexusReader.UI.Shared.Services
|
||||
@using NexusReader.UI.Shared.Components.Molecules
|
||||
@using NexusReader.UI.Shared.Components.Organisms
|
||||
@using Microsoft.Extensions.Logging
|
||||
@inject IPlatformService PlatformService
|
||||
@inject IFocusModeService FocusMode
|
||||
@inject IQuizStateService QuizService
|
||||
@inject IJSRuntime JS
|
||||
@inject IIdentityService IdentityService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject Microsoft.Extensions.Logging.ILogger<ReaderLayout> Logger
|
||||
@implements IDisposable
|
||||
|
||||
<div class="app-container @_platformClass @(FocusMode.IsFocusModeActive ? "focus-mode-active" : "")">
|
||||
<div class="reader-pane">
|
||||
<main>
|
||||
@Body
|
||||
</main>
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<ReaderFooter />
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
</div>
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<div class="resizer" id="sidebar-resizer"></div>
|
||||
|
||||
<div class="intelligence-sidebar">
|
||||
<IntelligenceToolbar />
|
||||
<div class="intelligence-content">
|
||||
<div class="intelligence-header">
|
||||
<div class="ai-title">
|
||||
<NexusIcon Name="robot" Size="20"
|
||||
Class="@($"neon-glow {(QuizService.HasNewQuiz ? "quiz-available" : "")}")" />
|
||||
<span>Asystent AI</span>
|
||||
</div>
|
||||
<button class="close-btn">×</button>
|
||||
</div>
|
||||
|
||||
<div class="intelligence-scroll-area">
|
||||
@if (!_isMobile)
|
||||
{
|
||||
<KnowledgeGraph />
|
||||
}
|
||||
<KnowledgeCheck />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Authorized>
|
||||
<Authorizing>
|
||||
<div class="app-preloader">
|
||||
<div class="preloader-spinner"></div>
|
||||
<div class="preloader-text">Weryfikacja...</div>
|
||||
</div>
|
||||
</Authorizing>
|
||||
</AuthorizeView>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui" data-nosnippet>
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string _platformClass = "platform-desktop";
|
||||
private bool _isMobile = false;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
FocusMode.OnFocusModeChanged += HandleUpdate;
|
||||
QuizService.OnQuizUpdated += HandleUpdate;
|
||||
|
||||
var context = PlatformService.GetDeviceContext();
|
||||
if (context.IsSuccess)
|
||||
{
|
||||
_isMobile = context.Value.DeviceType switch
|
||||
{
|
||||
DeviceType.Phone or DeviceType.Tablet => true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
_platformClass = _isMobile ? "platform-mobile" : "platform-desktop";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
try
|
||||
{
|
||||
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/NexusReader.UI.Shared/js/layoutResizer.js");
|
||||
await module.InvokeVoidAsync("initResizer", ".app-container", "#sidebar-resizer", "--sidebar-width");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed to initialize layout resizer JS module.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleUpdate() => InvokeAsync(StateHasChanged);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
FocusMode.OnFocusModeChanged -= HandleUpdate;
|
||||
QuizService.OnQuizUpdated -= HandleUpdate;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user