fix(di): resolve client-side WASM dependency injection validation errors on login
This commit is contained in:
+5
-5
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
<div class="loading-state" role="status" aria-label="Ładowanie rekomendacji">
|
<div @key='"loading"' class="loading-state" role="status" aria-label="Ładowanie rekomendacji">
|
||||||
<div class="spinner-ring">
|
<div class="spinner-ring">
|
||||||
<div class="spinner-track"></div>
|
<div class="spinner-track"></div>
|
||||||
<div class="spinner-head"></div>
|
<div class="spinner-head"></div>
|
||||||
@@ -25,24 +25,24 @@
|
|||||||
}
|
}
|
||||||
else if (_hasError)
|
else if (_hasError)
|
||||||
{
|
{
|
||||||
<div class="empty-state">
|
<div @key='"error"' class="empty-state">
|
||||||
<NexusIcon Name="alert-circle" Size="32" />
|
<NexusIcon Name="alert-circle" Size="32" />
|
||||||
<p>Nie udało się załadować rekomendacji.</p>
|
<p>Nie udało się załadować rekomendacji.</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else if (_recommendations is null || _recommendations.Count == 0)
|
else if (_recommendations is null || _recommendations.Count == 0)
|
||||||
{
|
{
|
||||||
<div class="empty-state">
|
<div @key='"empty"' class="empty-state">
|
||||||
<NexusIcon Name="book-open" Size="32" />
|
<NexusIcon Name="book-open" Size="32" />
|
||||||
<p>Zacznij czytać, aby odkryć powiązane tytuły.</p>
|
<p>Zacznij czytać, aby odkryć powiązane tytuły.</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ul class="recommendations-list" role="list">
|
<ul @key='"list"' class="recommendations-list" role="list">
|
||||||
@foreach (var rec in _recommendations)
|
@foreach (var rec in _recommendations)
|
||||||
{
|
{
|
||||||
<li class="recommendation-item @(rec.IsPremiumUpsell ? "premium" : "owned")"
|
<li @key="rec.TargetBookId" class="recommendation-item @(rec.IsPremiumUpsell ? "premium" : "owned")"
|
||||||
role="listitem">
|
role="listitem">
|
||||||
<div class="rec-content">
|
<div class="rec-content">
|
||||||
<div class="rec-meta">
|
<div class="rec-meta">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<section class="current-reading-card glass-panel">
|
<section class="current-reading-card glass-panel">
|
||||||
@if (Book != null)
|
@if (Book != null)
|
||||||
{
|
{
|
||||||
<div class="card-layout">
|
<div @key='"current-reading-book"' class="card-layout">
|
||||||
<div class="book-cover">
|
<div class="book-cover">
|
||||||
<img src="@(Book.CoverUrl ?? "https://via.placeholder.com/120x180?text=No+Cover")" alt="@Book.Title" />
|
<img src="@(Book.CoverUrl ?? "https://via.placeholder.com/120x180?text=No+Cover")" alt="@Book.Title" />
|
||||||
</div>
|
</div>
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="empty-state">
|
<div @key='"current-reading-empty"' class="empty-state">
|
||||||
<div class="empty-icon">
|
<div class="empty-icon">
|
||||||
<NexusIcon Name="book-open" Size="48" />
|
<NexusIcon Name="book-open" Size="48" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
|
|
||||||
@if (!_isFullyLoaded)
|
@if (!_isFullyLoaded)
|
||||||
{
|
{
|
||||||
<div class="app-preloader" style="backdrop-filter: blur(15px); background: rgba(18, 18, 18, 0.95); z-index: 100000; color: #ffffff;">
|
<div @key='"preloader"' class="app-preloader" style="backdrop-filter: blur(15px); background: rgba(18, 18, 18, 0.95); z-index: 100000; color: #ffffff;">
|
||||||
<div class="preloader-spinner"></div>
|
<div class="preloader-spinner"></div>
|
||||||
<div class="preloader-text" style="color: #ffffff;">Synchronizing Secure Session...</div>
|
<div class="preloader-text" style="color: #ffffff;">Synchronizing Secure Session...</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="hub-container @(_isMobileMenuOpen ? "mobile-menu-open" : "")">
|
<div @key='"hub-container"' class="hub-container @(_isMobileMenuOpen ? "mobile-menu-open" : "")">
|
||||||
<AuthorizeView>
|
<AuthorizeView>
|
||||||
<Authorized>
|
<Authorized>
|
||||||
<!-- Mobile Sticky Top-bar -->
|
<!-- Mobile Sticky Top-bar -->
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<span>lub</span>
|
<span>lub</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<EditForm Model="@_loginModel" OnValidSubmit="HandleLogin" class="auth-form">
|
<EditForm FormName="login-form" Model="@_loginModel" OnValidSubmit="HandleLogin" class="auth-form">
|
||||||
<DataAnnotationsValidator />
|
<DataAnnotationsValidator />
|
||||||
|
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="nexusLoginForm" method="post" action="/account/login-form" style="display:none">
|
<form @formname="hidden-login-form" id="nexusLoginForm" method="post" action="/account/login-form" style="display:none">
|
||||||
<input type="hidden" name="email" value="@_loginModel.Email" />
|
<input type="hidden" name="email" value="@_loginModel.Email" />
|
||||||
<input type="hidden" name="password" value="@_loginModel.Password" />
|
<input type="hidden" name="password" value="@_loginModel.Password" />
|
||||||
<input type="hidden" name="rememberMe" value="@(_loginModel.RememberMe ? "true" : "false")" />
|
<input type="hidden" name="rememberMe" value="@(_loginModel.RememberMe ? "true" : "false")" />
|
||||||
@@ -117,7 +117,10 @@
|
|||||||
[SupplyParameterFromQuery(Name = "returnUrl")]
|
[SupplyParameterFromQuery(Name = "returnUrl")]
|
||||||
public string? ReturnUrl { get; set; }
|
public string? ReturnUrl { get; set; }
|
||||||
|
|
||||||
private LoginModel _loginModel = new();
|
#pragma warning disable BL0008
|
||||||
|
[SupplyParameterFromForm(FormName = "login-form")]
|
||||||
|
private LoginModel _loginModel { get; set; } = new();
|
||||||
|
#pragma warning restore BL0008
|
||||||
private string? _errorMessage;
|
private string? _errorMessage;
|
||||||
private bool _isSubmitting;
|
private bool _isSubmitting;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<p class="auth-subtitle">Utwórz nowe konto</p>
|
<p class="auth-subtitle">Utwórz nowe konto</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<EditForm Model="@_registerModel" OnValidSubmit="HandleRegister" class="auth-form">
|
<EditForm FormName="register-form" Model="@_registerModel" OnValidSubmit="HandleRegister" class="auth-form">
|
||||||
<DataAnnotationsValidator />
|
<DataAnnotationsValidator />
|
||||||
|
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
@@ -71,14 +71,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="nexusLoginForm" method="post" action="/account/login-form" style="display:none">
|
<form @formname="hidden-register-login-form" id="nexusLoginForm" method="post" action="/account/login-form" style="display:none">
|
||||||
<input type="hidden" name="email" value="@_registerModel.Email" />
|
<input type="hidden" name="email" value="@_registerModel.Email" />
|
||||||
<input type="hidden" name="password" value="@_registerModel.Password" />
|
<input type="hidden" name="password" value="@_registerModel.Password" />
|
||||||
<input type="hidden" name="rememberMe" value="false" />
|
<input type="hidden" name="rememberMe" value="false" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@code {
|
@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 string? _errorMessage;
|
||||||
private bool _isSubmitting;
|
private bool _isSubmitting;
|
||||||
|
|
||||||
|
|||||||
@@ -61,24 +61,28 @@
|
|||||||
|
|
||||||
@if (_profile?.MappedConcepts != null && _profile.MappedConcepts.Any())
|
@if (_profile?.MappedConcepts != null && _profile.MappedConcepts.Any())
|
||||||
{
|
{
|
||||||
@for (int i = 0; i < _profile.MappedConcepts.Count; i++)
|
<div @key='"satellite-concepts-container"' style="display: contents;">
|
||||||
{
|
@for (int i = 0; i < _profile.MappedConcepts.Count; i++)
|
||||||
var concept = _profile.MappedConcepts[i];
|
{
|
||||||
var angle = i * (360.0 / _profile.MappedConcepts.Count);
|
var concept = _profile.MappedConcepts[i];
|
||||||
var dist = 65;
|
var angle = i * (360.0 / _profile.MappedConcepts.Count);
|
||||||
<div class="graph-node satellite"
|
var dist = 65;
|
||||||
style="--angle: @(angle)deg; --dist: @(dist)px;"
|
<div @key="concept.Id" class="graph-node satellite"
|
||||||
title="[@concept.Type] @concept.Content"
|
style="--angle: @(angle)deg; --dist: @(dist)px;"
|
||||||
@onmouseover="() => SetHoveredConcept(concept)"
|
title="[@concept.Type] @concept.Content"
|
||||||
@onmouseout="ClearHoveredConcept">
|
@onmouseover="() => SetHoveredConcept(concept)"
|
||||||
</div>
|
@onmouseout="ClearHoveredConcept">
|
||||||
}
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="graph-node satellite" style="--angle: 0deg; --dist: 60px;"></div>
|
<div @key='"satellite-placeholders-container"' style="display: contents;">
|
||||||
<div class="graph-node satellite" style="--angle: 120deg; --dist: 50px;"></div>
|
<div @key='"satellite-placeholder-0"' class="graph-node satellite" style="--angle: 0deg; --dist: 60px;"></div>
|
||||||
<div class="graph-node satellite" style="--angle: 240deg; --dist: 70px;"></div>
|
<div @key='"satellite-placeholder-1"' class="graph-node satellite" style="--angle: 120deg; --dist: 50px;"></div>
|
||||||
|
<div @key='"satellite-placeholder-2"' class="graph-node satellite" style="--angle: 240deg; --dist: 70px;"></div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="active-node-label">
|
<div class="active-node-label">
|
||||||
@@ -111,10 +115,10 @@
|
|||||||
<div class="quiz-preview">
|
<div class="quiz-preview">
|
||||||
@if (_profile?.RecentQuizzes != null && _profile.RecentQuizzes.Any())
|
@if (_profile?.RecentQuizzes != null && _profile.RecentQuizzes.Any())
|
||||||
{
|
{
|
||||||
<div class="quiz-history-list">
|
<div @key='"quiz-history-list"' class="quiz-history-list">
|
||||||
@foreach (var quiz in _profile.RecentQuizzes)
|
@foreach (var quiz in _profile.RecentQuizzes)
|
||||||
{
|
{
|
||||||
<div class="quiz-history-item">
|
<div @key="quiz.Id" class="quiz-history-item">
|
||||||
<div class="quiz-item-header">
|
<div class="quiz-item-header">
|
||||||
<span class="quiz-topic">@quiz.Topic</span>
|
<span class="quiz-topic">@quiz.Topic</span>
|
||||||
<span class="quiz-score badge @(quiz.Percentage >= 80 ? "badge-success" : quiz.Percentage >= 50 ? "badge-warning" : "badge-danger")">
|
<span class="quiz-score badge @(quiz.Percentage >= 80 ? "badge-success" : quiz.Percentage >= 50 ? "badge-warning" : "badge-danger")">
|
||||||
@@ -130,7 +134,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="empty-quiz-state">
|
<div @key='"empty-quiz-state"' class="empty-quiz-state">
|
||||||
<p class="question">Brak rozwiązanych quizów</p>
|
<p class="question">Brak rozwiązanych quizów</p>
|
||||||
<p class="sub-text">Rozwiązuj quizy w trakcie czytania książek, aby śledzić swoje postępy.</p>
|
<p class="sub-text">Rozwiązuj quizy w trakcie czytania książek, aby śledzić swoje postępy.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,24 +66,25 @@
|
|||||||
|
|
||||||
|
|
||||||
/* Global Semantic Theme Mapping */
|
/* Global Semantic Theme Mapping */
|
||||||
--nexus-primary: var(--nexus-neon);
|
:root {
|
||||||
--nexus-primary-glow: var(--nexus-neon-glow);
|
--nexus-primary: var(--nexus-neon);
|
||||||
--nexus-primary-hover: #00e688;
|
--nexus-primary-glow: var(--nexus-neon-glow);
|
||||||
|
--nexus-primary-hover: #00e688;
|
||||||
|
|
||||||
/* Standard Layout Tokens */
|
/* Standard Layout Tokens */
|
||||||
--radius-sm: 8px;
|
--radius-sm: 8px;
|
||||||
--radius-md: 12px;
|
--radius-md: 12px;
|
||||||
--radius-lg: 16px;
|
--radius-lg: 16px;
|
||||||
--radius-xl: 20px;
|
--radius-xl: 20px;
|
||||||
|
|
||||||
/* Safe Area Insets with fallbacks */
|
/* Safe Area Insets with fallbacks */
|
||||||
--safe-area-inset-top: env(safe-area-inset-top, 0px);
|
--safe-area-inset-top: env(safe-area-inset-top, 0px);
|
||||||
--safe-area-inset-bottom: env(safe-area-inset-bottom, 0px);
|
--safe-area-inset-bottom: env(safe-area-inset-bottom, 0px);
|
||||||
--safe-area-inset-left: env(safe-area-inset-left, 0px);
|
--safe-area-inset-left: env(safe-area-inset-left, 0px);
|
||||||
--safe-area-inset-right: env(safe-area-inset-right, 0px);
|
--safe-area-inset-right: env(safe-area-inset-right, 0px);
|
||||||
|
|
||||||
/* Transitions */
|
/* Transitions */
|
||||||
--nexus-transition: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
--nexus-transition: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Global Glassmorphism with Fallback */
|
/* Global Glassmorphism with Fallback */
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ builder.Services.AddSingleton<IQuizResultRepository>(new ThrowingQuizResultRepos
|
|||||||
builder.Services.AddSingleton<IConceptsMapReadRepository>(new ThrowingConceptsMapReadRepository());
|
builder.Services.AddSingleton<IConceptsMapReadRepository>(new ThrowingConceptsMapReadRepository());
|
||||||
builder.Services.AddSingleton<ISyncBroadcaster>(new ThrowingSyncBroadcaster());
|
builder.Services.AddSingleton<ISyncBroadcaster>(new ThrowingSyncBroadcaster());
|
||||||
builder.Services.AddSingleton<IEpubExtractor>(new ThrowingEpubExtractor());
|
builder.Services.AddSingleton<IEpubExtractor>(new ThrowingEpubExtractor());
|
||||||
|
builder.Services.AddSingleton<IUserLibraryStore>(new ThrowingUserLibraryStore());
|
||||||
|
builder.Services.AddSingleton<IVectorSearchStore>(new ThrowingVectorSearchStore());
|
||||||
|
builder.Services.Configure<NexusReader.Application.Common.RagMonetizationOptions>(builder.Configuration.GetSection(NexusReader.Application.Common.RagMonetizationOptions.SectionName));
|
||||||
|
builder.Services.AddSingleton<IChatClient>(new ThrowingChatClient());
|
||||||
|
|
||||||
builder.Services.AddApplication();
|
builder.Services.AddApplication();
|
||||||
builder.Services.AddScoped<IEpubReader, WasmEpubReader>();
|
builder.Services.AddScoped<IEpubReader, WasmEpubReader>();
|
||||||
@@ -135,3 +139,37 @@ public class ThrowingEpubExtractor : IEpubExtractor
|
|||||||
=> throw new NotSupportedException("EPUB text extraction is not supported in the WASM client.");
|
=> throw new NotSupportedException("EPUB text extraction is not supported in the WASM client.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ThrowingUserLibraryStore : IUserLibraryStore
|
||||||
|
{
|
||||||
|
public Task<List<Guid>> GetOwnedBookIdsAsync(string userId, CancellationToken cancellationToken = default)
|
||||||
|
=> throw new NotSupportedException("UserLibrary operations are not supported in the WASM client.");
|
||||||
|
|
||||||
|
public Task<Dictionary<Guid, string>> GetBookTitlesAsync(List<Guid> bookIds, CancellationToken cancellationToken = default)
|
||||||
|
=> throw new NotSupportedException("UserLibrary operations are not supported in the WASM client.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ThrowingVectorSearchStore : IVectorSearchStore
|
||||||
|
{
|
||||||
|
public Task<List<VectorChunk>> SearchGlobalAsync(string queryText, string tenantId, int limit, CancellationToken cancellationToken = default)
|
||||||
|
=> throw new NotSupportedException("VectorSearch operations are not supported in the WASM client.");
|
||||||
|
|
||||||
|
public Task<List<VectorChunk>> SearchLocalAsync(string queryText, string tenantId, List<Guid> whitelistedBookIds, int limit, CancellationToken cancellationToken = default)
|
||||||
|
=> throw new NotSupportedException("VectorSearch operations are not supported in the WASM client.");
|
||||||
|
|
||||||
|
public Task<List<VectorChunk>> 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<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> chatMessages, ChatOptions? options = null, CancellationToken cancellationToken = default)
|
||||||
|
=> throw new NotSupportedException("Chat operations are not supported in the WASM client.");
|
||||||
|
|
||||||
|
public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(IEnumerable<ChatMessage> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ builder.Services.AddHttpContextAccessor();
|
|||||||
builder.Services.AddScoped<IPlatformService, WebPlatformService>();
|
builder.Services.AddScoped<IPlatformService, WebPlatformService>();
|
||||||
builder.Services.AddScoped<INativeStorageService, NexusReader.Web.Services.NativeStorageService>();
|
builder.Services.AddScoped<INativeStorageService, NexusReader.Web.Services.NativeStorageService>();
|
||||||
builder.Services.AddScoped<IUserPreferenceStore, NexusReader.Web.Services.ServerUserPreferenceStore>();
|
builder.Services.AddScoped<IUserPreferenceStore, NexusReader.Web.Services.ServerUserPreferenceStore>();
|
||||||
|
builder.Services.AddScoped<IThemeService, NexusReader.Web.Services.ServerThemeService>();
|
||||||
|
builder.Services.AddScoped<IRecommendationService, NexusReader.Web.Services.ServerRecommendationService>();
|
||||||
// Feature settings (avoiding direct raw IConfiguration injection in client pages)
|
// Feature settings (avoiding direct raw IConfiguration injection in client pages)
|
||||||
var featureSettings = builder.Configuration.GetSection("Features").Get<FeatureSettings>() ?? new FeatureSettings();
|
var featureSettings = builder.Configuration.GetSection("Features").Get<FeatureSettings>() ?? new FeatureSettings();
|
||||||
builder.Services.AddSingleton(featureSettings);
|
builder.Services.AddSingleton(featureSettings);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server-side implementation of <see cref="IRecommendationService"/> that executes
|
||||||
|
/// the MediatR query directly inside the Web Server's request context.
|
||||||
|
/// </summary>
|
||||||
|
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<List<RecommendationDto>?> GetRecommendationsAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var httpContext = _httpContextAccessor.HttpContext;
|
||||||
|
if (httpContext?.User == null)
|
||||||
|
{
|
||||||
|
return new List<RecommendationDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
if (string.IsNullOrEmpty(userId))
|
||||||
|
{
|
||||||
|
return new List<RecommendationDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _mediator.Send(new GetContextualRecommendationsQuery(userId), cancellationToken);
|
||||||
|
if (result.IsSuccess && result.Value != null)
|
||||||
|
{
|
||||||
|
return result.Value.Recommendations;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new List<RecommendationDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ThemeMode>? OnThemeChanged
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync() => Task.CompletedTask;
|
||||||
|
public Task SetThemeAsync(ThemeMode mode) => Task.CompletedTask;
|
||||||
|
public Task ToggleTheme() => Task.CompletedTask;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user