feat(ui): implement hub navigation, profile dashboard and fix auth sync loop
- Added MainHubLayout with glassmorphism sidebar - Implemented Profile dashboard with learn metrics - Added request deduplication and caching to IdentityService - Fixed infinite redirect loop on /profile page - Added dashboard navigation from reader - Closes #26, Closes #27
This commit is contained in:
@@ -53,8 +53,6 @@ builder.Services.AddHttpClient("NexusAPI", client =>
|
||||
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("NexusAPI"));
|
||||
|
||||
builder.Services.AddScoped<IIdentityService, NexusReader.UI.Shared.Services.IdentityService>();
|
||||
builder.Services.AddScoped<NexusAuthenticationStateProvider>();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider>(sp => sp.GetRequiredService<NexusAuthenticationStateProvider>());
|
||||
builder.Services.AddCascadingAuthenticationState();
|
||||
|
||||
builder.Services.AddApplication();
|
||||
@@ -93,14 +91,24 @@ builder.Services.AddIdentityApiEndpoints<NexusUser>()
|
||||
builder.Services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
options.LoginPath = "/account/login";
|
||||
options.LogoutPath = "/account/logout";
|
||||
options.AccessDeniedPath = "/account/access-denied";
|
||||
options.Cookie.Name = "NexusReader.Auth";
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
||||
options.ExpireTimeSpan = TimeSpan.FromDays(30);
|
||||
options.SlidingExpiration = true;
|
||||
|
||||
options.Events.OnRedirectToLogin = context =>
|
||||
{
|
||||
if (context.Request.Path.StartsWithSegments("/api"))
|
||||
var isApiRequest = context.Request.Path.StartsWithSegments("/api") ||
|
||||
context.Request.Path.StartsWithSegments("/identity") ||
|
||||
context.Request.Headers["Accept"].ToString().Contains("application/json");
|
||||
|
||||
if (isApiRequest)
|
||||
{
|
||||
context.Response.StatusCode = 401;
|
||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -434,6 +442,7 @@ app.MapGet("/identity/profile", async (ClaimsPrincipal user, UserManager<NexusUs
|
||||
{
|
||||
Email = u.Email ?? string.Empty,
|
||||
AITokensUsed = u.AITokensUsed,
|
||||
TenantId = u.TenantId != null && u.TenantId.Length == 36 ? new Guid(u.TenantId) : Guid.Empty,
|
||||
Plan = u.SubscriptionPlan != null ? new SubscriptionPlanDto
|
||||
{
|
||||
Id = u.SubscriptionPlan.Id,
|
||||
@@ -441,7 +450,9 @@ app.MapGet("/identity/profile", async (ClaimsPrincipal user, UserManager<NexusUs
|
||||
AITokenLimit = u.SubscriptionPlan.AITokenLimit,
|
||||
MonthlyPrice = u.SubscriptionPlan.MonthlyPrice
|
||||
} : new SubscriptionPlanDto(),
|
||||
AverageQuizScore = u.QuizResults.Any() ? (int)u.QuizResults.Average(q => q.Percentage) : 0,
|
||||
AverageQuizScore = u.QuizResults.Any(q => q.TotalQuestions > 0)
|
||||
? (int)u.QuizResults.Where(q => q.TotalQuestions > 0).Average(q => (double)q.Score / q.TotalQuestions * 100)
|
||||
: 0,
|
||||
LastReadBook = u.Ebooks.OrderByDescending(e => e.LastReadDate).Select(e => new LastReadBookDto
|
||||
{
|
||||
Id = e.Id,
|
||||
|
||||
Reference in New Issue
Block a user