From 1eacf7ed93ab8cef9c628d398e7ec1a78313eee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Jasi=C5=84ski?= Date: Sun, 7 Jun 2026 13:56:09 +0200 Subject: [PATCH] fix(di): resolve client-side WASM dependency injection validation errors on login --- .../ContextualRecommendationsWidget.razor | 10 ++-- .../Organisms/CurrentReadingWidget.razor | 4 +- .../Layout/MainHubLayout.razor | 4 +- .../Pages/Account/Login.razor | 9 ++-- .../Pages/Account/Register.razor | 9 ++-- .../Pages/Dashboard.razor | 40 ++++++++------- src/NexusReader.UI.Shared/wwwroot/app.css | 31 ++++++------ src/NexusReader.Web.Client/Program.cs | 38 ++++++++++++++ src/NexusReader.Web/Program.cs | 2 + .../Services/ServerRecommendationService.cs | 50 +++++++++++++++++++ .../Services/ServerThemeService.cs | 21 ++++++++ 11 files changed, 170 insertions(+), 48 deletions(-) create mode 100644 src/NexusReader.Web/Services/ServerRecommendationService.cs create mode 100644 src/NexusReader.Web/Services/ServerThemeService.cs diff --git a/src/NexusReader.UI.Shared/Components/Organisms/ContextualRecommendationsWidget.razor b/src/NexusReader.UI.Shared/Components/Organisms/ContextualRecommendationsWidget.razor index 89626ab..26b8a66 100644 --- a/src/NexusReader.UI.Shared/Components/Organisms/ContextualRecommendationsWidget.razor +++ b/src/NexusReader.UI.Shared/Components/Organisms/ContextualRecommendationsWidget.razor @@ -15,7 +15,7 @@ @if (_isLoading) { -
+
@@ -25,24 +25,24 @@ } else if (_hasError) { -
+

Nie udało się załadować rekomendacji.

} else if (_recommendations is null || _recommendations.Count == 0) { -
+

Zacznij czytać, aby odkryć powiązane tytuły.

} else { -
    +
      @foreach (var rec in _recommendations) { -
    • diff --git a/src/NexusReader.UI.Shared/Components/Organisms/CurrentReadingWidget.razor b/src/NexusReader.UI.Shared/Components/Organisms/CurrentReadingWidget.razor index 2d493b5..c794857 100644 --- a/src/NexusReader.UI.Shared/Components/Organisms/CurrentReadingWidget.razor +++ b/src/NexusReader.UI.Shared/Components/Organisms/CurrentReadingWidget.razor @@ -5,7 +5,7 @@
      @if (Book != null) { -
      +
      @Book.Title
      @@ -51,7 +51,7 @@ } else { -
      +
      diff --git a/src/NexusReader.UI.Shared/Layout/MainHubLayout.razor b/src/NexusReader.UI.Shared/Layout/MainHubLayout.razor index 46b1de1..62239f2 100644 --- a/src/NexusReader.UI.Shared/Layout/MainHubLayout.razor +++ b/src/NexusReader.UI.Shared/Layout/MainHubLayout.razor @@ -6,13 +6,13 @@ @if (!_isFullyLoaded) { -
      +
      Synchronizing Secure Session...
      } -
      +
      diff --git a/src/NexusReader.UI.Shared/Pages/Account/Login.razor b/src/NexusReader.UI.Shared/Pages/Account/Login.razor index 5295a1f..a1c4e00 100644 --- a/src/NexusReader.UI.Shared/Pages/Account/Login.razor +++ b/src/NexusReader.UI.Shared/Pages/Account/Login.razor @@ -33,7 +33,7 @@ lub
      - +
      @@ -98,7 +98,7 @@
      -
      - +
      @@ -71,14 +71,17 @@
      - + @code { - private RegisterModel _registerModel = new(); +#pragma warning disable BL0008 + [SupplyParameterFromForm(FormName = "register-form")] + private RegisterModel _registerModel { get; set; } = new(); +#pragma warning restore BL0008 private string? _errorMessage; private bool _isSubmitting; diff --git a/src/NexusReader.UI.Shared/Pages/Dashboard.razor b/src/NexusReader.UI.Shared/Pages/Dashboard.razor index 4808e74..76be0eb 100644 --- a/src/NexusReader.UI.Shared/Pages/Dashboard.razor +++ b/src/NexusReader.UI.Shared/Pages/Dashboard.razor @@ -61,24 +61,28 @@ @if (_profile?.MappedConcepts != null && _profile.MappedConcepts.Any()) { - @for (int i = 0; i < _profile.MappedConcepts.Count; i++) - { - var concept = _profile.MappedConcepts[i]; - var angle = i * (360.0 / _profile.MappedConcepts.Count); - var dist = 65; -
      -
      - } +
      + @for (int i = 0; i < _profile.MappedConcepts.Count; i++) + { + var concept = _profile.MappedConcepts[i]; + var angle = i * (360.0 / _profile.MappedConcepts.Count); + var dist = 65; +
      +
      + } +
      } else { -
      -
      -
      +
      +
      +
      +
      +
      }
      @@ -111,10 +115,10 @@
      @if (_profile?.RecentQuizzes != null && _profile.RecentQuizzes.Any()) { -
      +
      @foreach (var quiz in _profile.RecentQuizzes) { -
      +
      @quiz.Topic = 50 ? "badge-warning" : "badge-danger")"> @@ -130,7 +134,7 @@ } else { -
      +

      Brak rozwiązanych quizów

      Rozwiązuj quizy w trakcie czytania książek, aby śledzić swoje postępy.

      diff --git a/src/NexusReader.UI.Shared/wwwroot/app.css b/src/NexusReader.UI.Shared/wwwroot/app.css index dee9bcc..81690bc 100644 --- a/src/NexusReader.UI.Shared/wwwroot/app.css +++ b/src/NexusReader.UI.Shared/wwwroot/app.css @@ -66,24 +66,25 @@ /* Global Semantic Theme Mapping */ ---nexus-primary: var(--nexus-neon); ---nexus-primary-glow: var(--nexus-neon-glow); ---nexus-primary-hover: #00e688; +:root { + --nexus-primary: var(--nexus-neon); + --nexus-primary-glow: var(--nexus-neon-glow); + --nexus-primary-hover: #00e688; -/* Standard Layout Tokens */ ---radius-sm: 8px; ---radius-md: 12px; ---radius-lg: 16px; ---radius-xl: 20px; + /* Standard Layout Tokens */ + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 20px; -/* Safe Area Insets with fallbacks */ ---safe-area-inset-top: env(safe-area-inset-top, 0px); ---safe-area-inset-bottom: env(safe-area-inset-bottom, 0px); ---safe-area-inset-left: env(safe-area-inset-left, 0px); ---safe-area-inset-right: env(safe-area-inset-right, 0px); + /* Safe Area Insets with fallbacks */ + --safe-area-inset-top: env(safe-area-inset-top, 0px); + --safe-area-inset-bottom: env(safe-area-inset-bottom, 0px); + --safe-area-inset-left: env(safe-area-inset-left, 0px); + --safe-area-inset-right: env(safe-area-inset-right, 0px); -/* Transitions */ ---nexus-transition: 0.4s cubic-bezier(0.4, 0, 0.2, 1); + /* Transitions */ + --nexus-transition: 0.4s cubic-bezier(0.4, 0, 0.2, 1); } /* Global Glassmorphism with Fallback */ diff --git a/src/NexusReader.Web.Client/Program.cs b/src/NexusReader.Web.Client/Program.cs index ac06c29..13666ce 100644 --- a/src/NexusReader.Web.Client/Program.cs +++ b/src/NexusReader.Web.Client/Program.cs @@ -62,6 +62,10 @@ builder.Services.AddSingleton(new ThrowingQuizResultRepos builder.Services.AddSingleton(new ThrowingConceptsMapReadRepository()); builder.Services.AddSingleton(new ThrowingSyncBroadcaster()); builder.Services.AddSingleton(new ThrowingEpubExtractor()); +builder.Services.AddSingleton(new ThrowingUserLibraryStore()); +builder.Services.AddSingleton(new ThrowingVectorSearchStore()); +builder.Services.Configure(builder.Configuration.GetSection(NexusReader.Application.Common.RagMonetizationOptions.SectionName)); +builder.Services.AddSingleton(new ThrowingChatClient()); builder.Services.AddApplication(); builder.Services.AddScoped(); @@ -135,3 +139,37 @@ public class ThrowingEpubExtractor : IEpubExtractor => throw new NotSupportedException("EPUB text extraction is not supported in the WASM client."); } +public class ThrowingUserLibraryStore : IUserLibraryStore +{ + public Task> GetOwnedBookIdsAsync(string userId, CancellationToken cancellationToken = default) + => throw new NotSupportedException("UserLibrary operations are not supported in the WASM client."); + + public Task> GetBookTitlesAsync(List bookIds, CancellationToken cancellationToken = default) + => throw new NotSupportedException("UserLibrary operations are not supported in the WASM client."); +} + +public class ThrowingVectorSearchStore : IVectorSearchStore +{ + public Task> SearchGlobalAsync(string queryText, string tenantId, int limit, CancellationToken cancellationToken = default) + => throw new NotSupportedException("VectorSearch operations are not supported in the WASM client."); + + public Task> SearchLocalAsync(string queryText, string tenantId, List whitelistedBookIds, int limit, CancellationToken cancellationToken = default) + => throw new NotSupportedException("VectorSearch operations are not supported in the WASM client."); + + public Task> SearchGlobalExcludeAsync(string queryText, string tenantId, Guid excludeBookId, int limit, CancellationToken cancellationToken = default) + => throw new NotSupportedException("VectorSearch operations are not supported in the WASM client."); +} + +public class ThrowingChatClient : IChatClient +{ + public void Dispose() { } + + public Task GetResponseAsync(IEnumerable chatMessages, ChatOptions? options = null, CancellationToken cancellationToken = default) + => throw new NotSupportedException("Chat operations are not supported in the WASM client."); + + public IAsyncEnumerable GetStreamingResponseAsync(IEnumerable chatMessages, ChatOptions? options = null, CancellationToken cancellationToken = default) + => throw new NotSupportedException("Chat operations are not supported in the WASM client."); + + public object? GetService(Type serviceType, object? serviceKey = null) => null; +} + diff --git a/src/NexusReader.Web/Program.cs b/src/NexusReader.Web/Program.cs index f684de9..dfe8d42 100644 --- a/src/NexusReader.Web/Program.cs +++ b/src/NexusReader.Web/Program.cs @@ -53,6 +53,8 @@ builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // Feature settings (avoiding direct raw IConfiguration injection in client pages) var featureSettings = builder.Configuration.GetSection("Features").Get() ?? new FeatureSettings(); builder.Services.AddSingleton(featureSettings); diff --git a/src/NexusReader.Web/Services/ServerRecommendationService.cs b/src/NexusReader.Web/Services/ServerRecommendationService.cs new file mode 100644 index 0000000..0724ba1 --- /dev/null +++ b/src/NexusReader.Web/Services/ServerRecommendationService.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using FluentResults; +using MediatR; +using Microsoft.AspNetCore.Http; +using NexusReader.Application.Queries.Recommendations; +using NexusReader.UI.Shared.Services; + +namespace NexusReader.Web.Services; + +/// +/// Server-side implementation of that executes +/// the MediatR query directly inside the Web Server's request context. +/// +public sealed class ServerRecommendationService : IRecommendationService +{ + private readonly IMediator _mediator; + private readonly IHttpContextAccessor _httpContextAccessor; + + public ServerRecommendationService(IMediator mediator, IHttpContextAccessor httpContextAccessor) + { + _mediator = mediator; + _httpContextAccessor = httpContextAccessor; + } + + public async Task?> GetRecommendationsAsync(CancellationToken cancellationToken = default) + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext?.User == null) + { + return new List(); + } + + var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrEmpty(userId)) + { + return new List(); + } + + var result = await _mediator.Send(new GetContextualRecommendationsQuery(userId), cancellationToken); + if (result.IsSuccess && result.Value != null) + { + return result.Value.Recommendations; + } + + return new List(); + } +} diff --git a/src/NexusReader.Web/Services/ServerThemeService.cs b/src/NexusReader.Web/Services/ServerThemeService.cs new file mode 100644 index 0000000..11df3ef --- /dev/null +++ b/src/NexusReader.Web/Services/ServerThemeService.cs @@ -0,0 +1,21 @@ +using NexusReader.Domain.Enums; +using NexusReader.UI.Shared.Services; + +namespace NexusReader.Web.Services; + +public sealed class ServerThemeService : IThemeService +{ + public ThemeMode Mode => ThemeMode.System; + public bool IsLightMode => false; + + // Explicit event implementation to avoid CS0067 warning about unused events on the server + public event Action? OnThemeChanged + { + add { } + remove { } + } + + public Task InitializeAsync() => Task.CompletedTask; + public Task SetThemeAsync(ThemeMode mode) => Task.CompletedTask; + public Task ToggleTheme() => Task.CompletedTask; +}