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
@@ -9,7 +9,7 @@
<div class="login-page-container">
<div class="mesh-bg"></div>
<div class="auth-card">
<div class="auth-header">
<div class="logo-box">
@@ -21,9 +21,8 @@
<div class="social-auth">
<button type="button" class="btn-google-auth" @onclick="HandleGoogleLogin">
<img src="https://www.gstatic.com/images/branding/product/1x/gsa_512dp.png"
alt="Google"
style="width: 20px !important; height: 20px !important; flex-shrink: 0;" />
<img src="https://www.gstatic.com/images/branding/product/1x/gsa_512dp.png" alt="Google"
style="width: 20px !important; height: 20px !important; flex-shrink: 0;" />
<span>Zaloguj się przez Google</span>
</button>
</div>
@@ -47,7 +46,8 @@
<div class="field-icon">
<NexusIcon Name="lock" Size="18" />
</div>
<InputText id="password" type="@(_showPassword ? "text" : "password")" @bind-Value="_loginModel.Password" placeholder="Hasło" class="field-input" />
<InputText id="password" type="@(_showPassword ? "text" : "password")"
@bind-Value="_loginModel.Password" placeholder="Hasło" class="field-input" />
<button type="button" class="toggle-visibility" @onclick="TogglePassword">
<NexusIcon Name="@(_showPassword ? "eye-off" : "eye")" Size="18" />
</button>
@@ -84,7 +84,8 @@
</div>
<div class="auth-legal">
Korzystając z usługi, akceptujesz <a href="/terms">Regulamin</a> i <a href="/privacy">Politykę Prywatności</a>
Korzystając z usługi, akceptujesz <a href="/terms">Regulamin</a> i <a href="/privacy">Politykę
Prywatności</a>
</div>
</div>
</div>
@@ -121,12 +122,24 @@
try
{
var success = await IdentityService.LoginAsync(_loginModel.Email, _loginModel.Password, _loginModel.RememberMe);
if (success) NavigationManager.NavigateTo("/");
else _errorMessage = "Nieprawidłowy e-mail lub hasło.";
var result = await IdentityService.LoginAsync(_loginModel.Email, _loginModel.Password, _loginModel.RememberMe);
if (result.IsSuccess)
{
NavigationManager.NavigateTo("/");
}
else
{
_errorMessage = result.Errors.FirstOrDefault()?.Message ?? "Nieprawidłowy e-mail lub hasło.";
}
}
catch (Exception ex)
{
_errorMessage = $"Wystąpił błąd logowania: {ex.Message}.";
}
finally
{
_isSubmitting = false;
}
catch (Exception) { _errorMessage = "Wystąpił błąd logowania."; }
finally { _isSubmitting = false; }
}
private void HandleGoogleLogin() => NavigationManager.NavigateTo("identity/login/google", forceLoad: true);