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,147 @@
|
||||
@page "/"
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using NexusReader.UI.Shared.Components.Atoms
|
||||
@using NexusReader.UI.Shared.Services
|
||||
@inject IIdentityService IdentityService
|
||||
@inject NavigationManager NavigationManager
|
||||
@attribute [Authorize]
|
||||
|
||||
<PageTitle>Dashboard | Nexus Reader</PageTitle>
|
||||
|
||||
<div class="dashboard-container">
|
||||
<!-- Top Profile Section -->
|
||||
<header class="profile-header">
|
||||
<div class="header-grid-bg"></div>
|
||||
<div class="profile-visual">
|
||||
<div class="avatar-wrapper">
|
||||
<img src="https://api.dicebear.com/7.x/bottts/svg?seed=Nexus" alt="Profile" class="profile-img" />
|
||||
<div class="avatar-glow"></div>
|
||||
</div>
|
||||
<h1 class="username">[User_Explorer1988]</h1>
|
||||
|
||||
<div class="status-pills">
|
||||
<div class="status-pill">
|
||||
<span class="pill-label">Books Read:</span>
|
||||
<span class="pill-value">12</span>
|
||||
</div>
|
||||
<div class="status-pill">
|
||||
<span class="pill-label">Concepts Mapped:</span>
|
||||
<span class="pill-value">450</span>
|
||||
</div>
|
||||
<div class="status-pill">
|
||||
<span class="pill-label">Quiz Mastery:</span>
|
||||
<span class="pill-value">88%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="dashboard-content">
|
||||
<h2 class="section-title">Witaj, @(_profile?.Email.Split('@')[0] ?? "Użytkowniku")</h2>
|
||||
|
||||
<div class="main-grid">
|
||||
<!-- Current Reading Card -->
|
||||
<section class="reading-card glass-panel">
|
||||
@if (_profile?.LastReadBook != null)
|
||||
{
|
||||
<div class="card-header">
|
||||
<h3>Ostatnio czytane: @_profile.LastReadBook.Title</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="reading-thumb">
|
||||
<img src="@(_profile.LastReadBook.CoverUrl ?? "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/402px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg")" alt="Current Book" />
|
||||
</div>
|
||||
<div class="reading-info">
|
||||
<div class="progress-section">
|
||||
<span class="chapter-label">@_profile.LastReadBook.LastChapter</span>
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" style="width: @(_profile.LastReadBook.Progress.ToString("F0", System.Globalization.CultureInfo.InvariantCulture))%">
|
||||
<div class="progress-bubble">@(_profile.LastReadBook.Progress.ToString("F1"))%</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="progress-detail">Postęp: @(_profile.LastReadBook.Progress.ToString("F2"))% - @_profile.LastReadBook.Author.Name</span>
|
||||
</div>
|
||||
<p class="reading-desc">
|
||||
Kontynuuj odkrywanie wiedzy w książce "@_profile.LastReadBook.Title".
|
||||
Twój cyfrowy asystent Nexus jest gotowy do analizy kolejnych rozdziałów i generowania interaktywnych map myśli.
|
||||
</p>
|
||||
<div class="card-actions">
|
||||
<button class="btn-nexus primary" @onclick='() => NavigationManager.NavigateTo($"/reader?chapter={_profile.LastReadBook.LastChapterIndex}")'>Kontynuuj Czytanie</button>
|
||||
<button class="btn-nexus secondary" @onclick='() => NavigationManager.NavigateTo("/library")'>Moja Biblioteka</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card-header">
|
||||
<h3>Brak aktywnych lektur</h3>
|
||||
</div>
|
||||
<div class="card-body empty-state">
|
||||
<div class="empty-icon">
|
||||
<NexusIcon Name="book-open" Size="64" />
|
||||
</div>
|
||||
<div class="reading-info">
|
||||
<p class="reading-desc">
|
||||
Nie czytasz obecnie żadnej książki. Przejdź do biblioteki, aby przesłać swój pierwszy plik EPUB i rozpocząć przygodę z Nexus Reader.
|
||||
</p>
|
||||
<div class="card-actions">
|
||||
<button class="btn-nexus primary" @onclick='() => NavigationManager.NavigateTo("/library")'>Przejdź do Biblioteki</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
<div class="secondary-grid">
|
||||
<!-- Knowledge Integration -->
|
||||
<section class="integration-card glass-panel">
|
||||
<div class="panel-header">
|
||||
<h4>Knowledge Integration Progress</h4>
|
||||
<NexusIcon Name="arrow-right" Size="16" />
|
||||
</div>
|
||||
<div class="graph-placeholder">
|
||||
<div class="graph-node central"></div>
|
||||
<div class="graph-node satellite" style="--angle: 0deg; --dist: 60px;"></div>
|
||||
<div class="graph-node satellite" style="--angle: 120deg; --dist: 50px;"></div>
|
||||
<div class="graph-node satellite" style="--angle: 240deg; --dist: 70px;"></div>
|
||||
<div class="active-node-label">TU JESTEŚ</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Quiz Summary -->
|
||||
<section class="quiz-card glass-panel">
|
||||
<div class="panel-header">
|
||||
<h4>Quiz Summary: Key Thinkers</h4>
|
||||
<NexusIcon Name="arrow-right" Size="16" />
|
||||
</div>
|
||||
<div class="quiz-preview">
|
||||
<p class="question">Który artysta namalował 'Ostatnią Wieczerzę'?</p>
|
||||
<div class="quiz-options">
|
||||
<div class="quiz-option active">
|
||||
<span class="option-letter">A)</span> Michal Anioł
|
||||
</div>
|
||||
<div class="quiz-option">
|
||||
<span class="option-letter">B)</span> Leonardo da Vinci
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private UserProfile? _profile;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var result = await IdentityService.GetProfileAsync();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_profile = result.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user