diff --git a/WardrobeManager.Api.Tests/Endpoints/IdentityEndpointsTests.cs b/WardrobeManager.Api.Tests/Endpoints/IdentityEndpointsTests.cs index e21bf2f..96cb205 100644 --- a/WardrobeManager.Api.Tests/Endpoints/IdentityEndpointsTests.cs +++ b/WardrobeManager.Api.Tests/Endpoints/IdentityEndpointsTests.cs @@ -68,9 +68,9 @@ public async Task DoesAdminUserExist_WhenAdminDoesNotExist_ReturnsFalseResult() public async Task CreateAdminIfMissing_WhenCreatedSuccessfully_ReturnsCreated() { // Arrange - var credentials = new AdminUserCredentials { email = "admin@test.com", password = "SecurePass1!" }; + var credentials = new AuthenticationCredentialsModel { Email = "admin@test.com", Password = "SecurePass1!" }; _mockUserService - .Setup(s => s.CreateAdminIfMissing("admin@test.com", "SecurePass1!")) + .Setup(s => s.CreateAdminIfMissing(credentials)) .ReturnsAsync((true, "Admin user created!")); // Act @@ -84,9 +84,9 @@ public async Task CreateAdminIfMissing_WhenCreatedSuccessfully_ReturnsCreated() public async Task CreateAdminIfMissing_WhenAdminAlreadyExists_ReturnsConflict() { // Arrange - var credentials = new AdminUserCredentials { email = "admin@test.com", password = "SecurePass1!" }; + var credentials = new AuthenticationCredentialsModel { Email = "admin@test.com", Password = "SecurePass1!" }; _mockUserService - .Setup(s => s.CreateAdminIfMissing("admin@test.com", "SecurePass1!")) + .Setup(s => s.CreateAdminIfMissing(credentials)) .ReturnsAsync((false, "Admin user already exists!")); // Act diff --git a/WardrobeManager.Api.Tests/Services/ClothingServiceTests.cs b/WardrobeManager.Api.Tests/Services/ClothingServiceTests.cs index 9ecfab6..d56d102 100644 --- a/WardrobeManager.Api.Tests/Services/ClothingServiceTests.cs +++ b/WardrobeManager.Api.Tests/Services/ClothingServiceTests.cs @@ -11,6 +11,7 @@ using WardrobeManager.Api.Services.Interfaces; using WardrobeManager.Shared.DTOs; using WardrobeManager.Shared.Enums; +using WardrobeManager.Shared.Services; using WardrobeManager.Shared.StaticResources; namespace WardrobeManager.Api.Tests.Services; diff --git a/WardrobeManager.Api.Tests/Services/UserServiceTests.cs b/WardrobeManager.Api.Tests/Services/UserServiceTests.cs index 5575967..b63e462 100644 --- a/WardrobeManager.Api.Tests/Services/UserServiceTests.cs +++ b/WardrobeManager.Api.Tests/Services/UserServiceTests.cs @@ -6,6 +6,7 @@ using WardrobeManager.Api.Services.Implementation; using WardrobeManager.Api.Tests.Helpers; using WardrobeManager.Shared.DTOs; +using WardrobeManager.Shared.Models; namespace WardrobeManager.Api.Tests.Services; @@ -196,9 +197,14 @@ public async Task CreateAdminIfMissing_WhenAdminAlreadyExists_ReturnsFalseWithMe _mockUserManager.Setup(m => m.Users).Returns(users); _mockRoleManager.Setup(m => m.RoleExistsAsync("Admin")).ReturnsAsync(true); _mockUserManager.Setup(m => m.IsInRoleAsync(adminUser, "Admin")).ReturnsAsync(true); + var credentials = new AuthenticationCredentialsModel + { + Email = "admin@test.com", + Password = "password" + }; // Act - var result = await _service.CreateAdminIfMissing("admin@test.com", "password"); + var result = await _service.CreateAdminIfMissing(credentials); // Assert using (new AssertionScope()) @@ -221,9 +227,14 @@ public async Task CreateAdminIfMissing_WhenAdminDoesNotExist_CreatesAdminAndRetu _mockUserManager .Setup(m => m.AddToRoleAsync(It.IsAny(), "Admin")) .ReturnsAsync(IdentityResult.Success); + var credentials = new AuthenticationCredentialsModel + { + Email = "admin@test.com", + Password = "SecurePass1!" + }; // Act - var result = await _service.CreateAdminIfMissing("admin@test.com", "SecurePass1!"); + var result = await _service.CreateAdminIfMissing(credentials); // Assert using (new AssertionScope()) diff --git a/WardrobeManager.Api/Endpoints/IdentityEndpoints.cs b/WardrobeManager.Api/Endpoints/IdentityEndpoints.cs index d2f318d..1630a18 100644 --- a/WardrobeManager.Api/Endpoints/IdentityEndpoints.cs +++ b/WardrobeManager.Api/Endpoints/IdentityEndpoints.cs @@ -7,6 +7,7 @@ using WardrobeManager.Api.Services.Implementation; using WardrobeManager.Api.Services.Interfaces; using WardrobeManager.Shared.Models; +using WardrobeManager.Shared.StaticResources; #endregion @@ -68,17 +69,23 @@ public static async Task DoesAdminUserExist(IUserService userService) } public static async Task CreateAdminIfMissing(IUserService userService, - [FromBody] AdminUserCredentials credentials) + [FromBody] AuthenticationCredentialsModel credentials) { - var res = await userService.CreateAdminIfMissing(credentials.email, credentials.password); + var res = StaticValidators.Validate(credentials); + if (!res.Success) + { + return Results.BadRequest(res.Message); + } + + var result = await userService.CreateAdminIfMissing(credentials); - if (res.Item1) + if (result.Item1) { return Results.Created(); } else { - return Results.Conflict(res.Item2); + return Results.Conflict(result.Item2); } } } \ No newline at end of file diff --git a/WardrobeManager.Api/Program.cs b/WardrobeManager.Api/Program.cs index 936fed3..5f2f047 100644 --- a/WardrobeManager.Api/Program.cs +++ b/WardrobeManager.Api/Program.cs @@ -13,6 +13,7 @@ using WardrobeManager.Api.Services.Implementation; using WardrobeManager.Api.Services.Interfaces; using WardrobeManager.Shared.DTOs; +using WardrobeManager.Shared.Services; using WardrobeManager.Shared.StaticResources; #endregion diff --git a/WardrobeManager.Api/Properties/launchSettings.json b/WardrobeManager.Api/Properties/launchSettings.json index f081300..7dccebf 100644 --- a/WardrobeManager.Api/Properties/launchSettings.json +++ b/WardrobeManager.Api/Properties/launchSettings.json @@ -15,7 +15,8 @@ "launchBrowser": false, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_WATCH_RESTART_ON_RUDE_EDIT": "1" }, "dotnetRunMessages": true, "applicationUrl": "https://localhost:7026;http://localhost:5054" diff --git a/WardrobeManager.Api/Services/Implementation/ClothingService.cs b/WardrobeManager.Api/Services/Implementation/ClothingService.cs index 153d2df..3ed8347 100644 --- a/WardrobeManager.Api/Services/Implementation/ClothingService.cs +++ b/WardrobeManager.Api/Services/Implementation/ClothingService.cs @@ -11,6 +11,7 @@ using WardrobeManager.Shared.DTOs; using WardrobeManager.Shared.Enums; using WardrobeManager.Shared.Models; +using WardrobeManager.Shared.Services; using WardrobeManager.Shared.StaticResources; #endregion diff --git a/WardrobeManager.Api/Services/Implementation/UserService.cs b/WardrobeManager.Api/Services/Implementation/UserService.cs index 42ee7ae..8667ffb 100644 --- a/WardrobeManager.Api/Services/Implementation/UserService.cs +++ b/WardrobeManager.Api/Services/Implementation/UserService.cs @@ -11,6 +11,7 @@ using WardrobeManager.Shared.DTOs; using WardrobeManager.Shared.Enums; using WardrobeManager.Shared.Models; +using WardrobeManager.Shared.StaticResources; #endregion @@ -103,7 +104,7 @@ public async Task DoesAdminUserExist() return false; } - public async Task<(bool, string)> CreateAdminIfMissing(string email, string password) + public async Task<(bool, string)> CreateAdminIfMissing(AuthenticationCredentialsModel credentials) { if (await DoesAdminUserExist()) { @@ -115,13 +116,13 @@ public async Task DoesAdminUserExist() var user = new User { - Email = email, - NormalizedEmail = email.ToUpper(), - UserName = email.ToUpper(), - NormalizedUserName = email.ToUpper(), + Email = credentials.Email, + NormalizedEmail = credentials.Email.ToUpper(ProjectConstants.DefaultCultureInfo), + UserName = credentials.Email.ToUpper(ProjectConstants.DefaultCultureInfo), + NormalizedUserName = credentials.Email.ToUpper(ProjectConstants.DefaultCultureInfo), }; - var createResult = await _userManager.CreateAsync(user, password); + var createResult = await _userManager.CreateAsync(user, credentials.Password); if (createResult.Succeeded is false) { diff --git a/WardrobeManager.Api/Services/Interfaces/IUserService.cs b/WardrobeManager.Api/Services/Interfaces/IUserService.cs index 7aba802..04665b7 100644 --- a/WardrobeManager.Api/Services/Interfaces/IUserService.cs +++ b/WardrobeManager.Api/Services/Interfaces/IUserService.cs @@ -12,5 +12,5 @@ public interface IUserService Task UpdateUser(string userId, EditedUserDTO editedUser); Task DeleteUser(string userId); Task DoesAdminUserExist(); - Task<(bool, string)> CreateAdminIfMissing(string email, string password); + Task<(bool, string)> CreateAdminIfMissing(AuthenticationCredentialsModel credentials); } \ No newline at end of file diff --git a/WardrobeManager.Presentation.Tests/ApiServiceTests.cs b/WardrobeManager.Presentation.Tests/ApiServiceTests.cs index 946ebbb..69100dd 100644 --- a/WardrobeManager.Presentation.Tests/ApiServiceTests.cs +++ b/WardrobeManager.Presentation.Tests/ApiServiceTests.cs @@ -5,6 +5,7 @@ using Moq.Protected; using WardrobeManager.Presentation.Services.Implementation; using WardrobeManager.Presentation.Services.Interfaces; +using WardrobeManager.Shared.Models; namespace WardrobeManager.Presentation.Tests; @@ -178,10 +179,10 @@ public async Task AddLog_WhenCalled_PostsToAddLogEndpoint() public async Task CreateAdminUserIfMissing_WhenSuccessful_ReturnsTrueWithMessage() { // Arrange - var credentials = new WardrobeManager.Shared.Models.AdminUserCredentials + var credentials = new AuthenticationCredentialsModel() { - email = "admin@test.com", - password = "SecurePass1!" + Email = "admin@test.com", + Password = "SecurePass1!" }; var mockResponse = new HttpResponseMessage { @@ -206,8 +207,8 @@ public async Task CreateAdminUserIfMissing_WhenSuccessful_ReturnsTrueWithMessage // Assert using (new FluentAssertions.Execution.AssertionScope()) { - result.Item1.Should().BeTrue(); - result.Item2.Should().Be("Admin user created!"); + result.Success.Should().BeTrue(); + result.Message.Should().Be("Admin user created!"); } } @@ -215,10 +216,10 @@ public async Task CreateAdminUserIfMissing_WhenSuccessful_ReturnsTrueWithMessage public async Task CreateAdminUserIfMissing_WhenAdminAlreadyExists_ReturnsFalseWithMessage() { // Arrange - var credentials = new WardrobeManager.Shared.Models.AdminUserCredentials + var credentials = new AuthenticationCredentialsModel { - email = "admin@test.com", - password = "SecurePass1!" + Email = "admin@test.com", + Password = "SecurePass1!" }; var mockResponse = new HttpResponseMessage { @@ -243,8 +244,8 @@ public async Task CreateAdminUserIfMissing_WhenAdminAlreadyExists_ReturnsFalseWi // Assert using (new FluentAssertions.Execution.AssertionScope()) { - result.Item1.Should().BeFalse(); - result.Item2.Should().Be("Admin user already exists!"); + result.Success.Should().BeFalse(); + result.Message.Should().Be("Admin user already exists!"); } } diff --git a/WardrobeManager.Presentation.Tests/ViewModels/AddClothingItemViewModelTests.cs b/WardrobeManager.Presentation.Tests/ViewModels/AddClothingItemViewModelTests.cs index ac8f055..bb6177a 100644 --- a/WardrobeManager.Presentation.Tests/ViewModels/AddClothingItemViewModelTests.cs +++ b/WardrobeManager.Presentation.Tests/ViewModels/AddClothingItemViewModelTests.cs @@ -8,6 +8,8 @@ using WardrobeManager.Shared.StaticResources; using Blazing.Mvvm.Components; using Microsoft.Extensions.Configuration; +using WardrobeManager.Presentation.ViewModels.Pages; +using WardrobeManager.Shared.Services; namespace WardrobeManager.Presentation.Tests.ViewModels; diff --git a/WardrobeManager.Presentation.Tests/ViewModels/DashboardViewModelTests.cs b/WardrobeManager.Presentation.Tests/ViewModels/DashboardViewModelTests.cs index 5d773d0..b18a7f6 100644 --- a/WardrobeManager.Presentation.Tests/ViewModels/DashboardViewModelTests.cs +++ b/WardrobeManager.Presentation.Tests/ViewModels/DashboardViewModelTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using WardrobeManager.Presentation.ViewModels; +using WardrobeManager.Presentation.ViewModels.Pages; namespace WardrobeManager.Presentation.Tests.ViewModels; diff --git a/WardrobeManager.Presentation.Tests/ViewModels/HomeViewModelTests.cs b/WardrobeManager.Presentation.Tests/ViewModels/HomeViewModelTests.cs index 57b4046..35b1f91 100644 --- a/WardrobeManager.Presentation.Tests/ViewModels/HomeViewModelTests.cs +++ b/WardrobeManager.Presentation.Tests/ViewModels/HomeViewModelTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using WardrobeManager.Presentation.ViewModels; +using WardrobeManager.Presentation.ViewModels.Pages; namespace WardrobeManager.Presentation.Tests.ViewModels; diff --git a/WardrobeManager.Presentation.Tests/ViewModels/LoginViewModelTests.cs b/WardrobeManager.Presentation.Tests/ViewModels/LoginViewModelTests.cs index 8779bb3..884eea0 100644 --- a/WardrobeManager.Presentation.Tests/ViewModels/LoginViewModelTests.cs +++ b/WardrobeManager.Presentation.Tests/ViewModels/LoginViewModelTests.cs @@ -11,6 +11,7 @@ using WardrobeManager.Shared.Models; using Microsoft.AspNetCore.Components.Authorization; using System.Security.Claims; +using WardrobeManager.Presentation.ViewModels.Pages; namespace WardrobeManager.Presentation.Tests.ViewModels; diff --git a/WardrobeManager.Presentation.Tests/ViewModels/NavBarViewModelTests.cs b/WardrobeManager.Presentation.Tests/ViewModels/NavBarViewModelTests.cs index 957ef5e..3044803 100644 --- a/WardrobeManager.Presentation.Tests/ViewModels/NavBarViewModelTests.cs +++ b/WardrobeManager.Presentation.Tests/ViewModels/NavBarViewModelTests.cs @@ -7,6 +7,8 @@ using WardrobeManager.Presentation.ViewModels; using WardrobeManager.Presentation.Tests.Helpers; using Microsoft.AspNetCore.Components.Authorization; +using WardrobeManager.Presentation.ViewModels.Components; +using WardrobeManager.Presentation.ViewModels.Pages; namespace WardrobeManager.Presentation.Tests.ViewModels; diff --git a/WardrobeManager.Presentation.Tests/ViewModels/SignupViewModelTests.cs b/WardrobeManager.Presentation.Tests/ViewModels/SignupViewModelTests.cs index 722d1b7..69d2952 100644 --- a/WardrobeManager.Presentation.Tests/ViewModels/SignupViewModelTests.cs +++ b/WardrobeManager.Presentation.Tests/ViewModels/SignupViewModelTests.cs @@ -7,6 +7,7 @@ using WardrobeManager.Presentation.Identity.Models; using WardrobeManager.Presentation.Services.Interfaces; using WardrobeManager.Presentation.ViewModels; +using WardrobeManager.Presentation.ViewModels.Pages; using WardrobeManager.Shared.Enums; using WardrobeManager.Shared.Models; @@ -108,6 +109,7 @@ public async Task SignupAsync_WhenSignupFails_DoesNotAttemptLogin() public async Task SignupAsync_WhenSignupSucceedsButLoginFails_AddsErrorNotification() { // Arrange + _viewModel.AuthenticationCredentialsModel = new AuthenticationCredentialsModel { Email = "test@gmail.com", Password = "SecurePassword1!" }; _mockIdentityService .Setup(s => s.SignupAsync(It.IsAny())) .ReturnsAsync(true); diff --git a/WardrobeManager.Presentation.Tests/ViewModels/WardrobeViewModelTests.cs b/WardrobeManager.Presentation.Tests/ViewModels/WardrobeViewModelTests.cs index fadc699..c0fae7a 100644 --- a/WardrobeManager.Presentation.Tests/ViewModels/WardrobeViewModelTests.cs +++ b/WardrobeManager.Presentation.Tests/ViewModels/WardrobeViewModelTests.cs @@ -6,6 +6,7 @@ using WardrobeManager.Presentation.Tests.Helpers; using WardrobeManager.Shared.DTOs; using Blazing.Mvvm.Components; +using WardrobeManager.Presentation.ViewModels.Pages; namespace WardrobeManager.Presentation.Tests.ViewModels; diff --git a/WardrobeManager.Presentation/Components/Onboarding/OnboardingSection.razor b/WardrobeManager.Presentation/Components/Onboarding/OnboardingSection.razor index c16e31c..341b0e6 100644 --- a/WardrobeManager.Presentation/Components/Onboarding/OnboardingSection.razor +++ b/WardrobeManager.Presentation/Components/Onboarding/OnboardingSection.razor @@ -1,14 +1,42 @@ @namespace WardrobeManager.Presentation.Components.Onboarding -
-

@Title

- @ChildContent - +
+
+

@Title

+ @ChildContent +
+
+
+ + + } +
@code { [Parameter] public required string Title { get; set; } [Parameter] public required RenderFragment ChildContent { get; set; } - [Parameter] public required string ButtonText { get; set; } - [Parameter] public required EventCallback ButtonClickCallback { get; set; } + [Parameter] public required EventCallback NextButtonClickCallback { get; set; } + [Parameter] public required EventCallback PreviousButtonClickCallback { get; set; } + [Parameter] public EventCallback SkipTutorialButtonClickCallback { get; set; } + private bool ShowSkipTutorialDialog { get; set; } } \ No newline at end of file diff --git a/WardrobeManager.Presentation/Components/Shared/Notifications.razor b/WardrobeManager.Presentation/Components/Shared/Notifications.razor index 0b528f5..5295370 100644 --- a/WardrobeManager.Presentation/Components/Shared/Notifications.razor +++ b/WardrobeManager.Presentation/Components/Shared/Notifications.razor @@ -1,62 +1,50 @@ @namespace WardrobeManager.Presentation.Components.Shared -@inject INotificationService _notificationService +@inherits MvvmComponentBase +
-
- @foreach (var notification in _notificationService.Notifications) - { - var cssClass = GetNotificationButtonType(notification.Type); - - - @notification.Message - - } -
- -@code { - - protected override void OnInitialized() - { - _notificationService.OnChange += StateHasChanged; - // this runs the callback method supplied every 5 seconds. it does not wait for the last - // call to the method to finish. this can cause race conditions. it should be fine though - var _timer = new Timer(TryToClearNotifications, null, TimeSpan.Zero, TimeSpan.FromSeconds(5)); - } - - // Go through all notifications and try to remove the non-critical ones - private void TryToClearNotifications(object? state) + @if (_notificationService.Notifications.Count > 5) { - var notifications = _notificationService.Notifications; - - foreach (var notification in notifications) - { - TimeSpan difference = DateTime.UtcNow - notification.CreationDate; - - if (difference.TotalSeconds > 10 && notification.Type != NotificationType.Warning && notification.Type != NotificationType.Error) - { - _notificationService.RemoveNotification(notification); - } - } +
+ + + +
+
+

Notification

+

Type: @notification.Type.ToString()

+

Created: @notification.CreationDate

+
+
+ + Message: + @notification.Message + + +
+
+
+
+
} + - public void Dispose() - { - _notificationService.OnChange -= StateHasChanged; - } +@code { - public ButtonType GetNotificationButtonType(NotificationType type) - { - return type switch - { - NotificationType.Error => ButtonType.Destructive, - _ => ButtonType.Outline - }; - } } diff --git a/WardrobeManager.Presentation/CustomHttpMessageHandler.cs b/WardrobeManager.Presentation/CustomHttpMessageHandler.cs index e52288a..fb99aa8 100644 --- a/WardrobeManager.Presentation/CustomHttpMessageHandler.cs +++ b/WardrobeManager.Presentation/CustomHttpMessageHandler.cs @@ -1,5 +1,6 @@ #region +using System.Net; using Microsoft.AspNetCore.Components.WebAssembly.Http; using WardrobeManager.Presentation.Services.Interfaces; using WardrobeManager.Shared.Enums; @@ -29,9 +30,11 @@ protected override async Task SendAsync(HttpRequestMessage { // This sends the actual HTTP request var response = await base.SendAsync(request, cancellationToken); - - // 1. Check for standard HTTP errors (like 404 Not Found, 500 Server Error) - if (!response.IsSuccessStatusCode) + + // The application continually checks the authentication state of the user via GetAuthenticationStateAsync + // The method calls the backend. If the user is not authenticated the backend returns HTTP 401. + // We don't want to notify the user of this as it is benign + if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.Unauthorized) { notificationService.AddNotification($"Server Error: {(int)response.StatusCode}", NotificationType.Error); } diff --git a/WardrobeManager.Presentation/Identity/CookieAuthenticationStateProvider.cs b/WardrobeManager.Presentation/Identity/CookieAuthenticationStateProvider.cs index d8115bd..5a9da25 100644 --- a/WardrobeManager.Presentation/Identity/CookieAuthenticationStateProvider.cs +++ b/WardrobeManager.Presentation/Identity/CookieAuthenticationStateProvider.cs @@ -164,7 +164,6 @@ public override async Task GetAuthenticationStateAsync() // default to not authenticated var user = Unauthenticated; - try { // the user info endpoint is secured, so if the user isn't logged in this will fail diff --git a/WardrobeManager.Presentation/Layout/MainLayout.razor b/WardrobeManager.Presentation/Layout/MainLayout.razor index b25beee..8811cf6 100644 --- a/WardrobeManager.Presentation/Layout/MainLayout.razor +++ b/WardrobeManager.Presentation/Layout/MainLayout.razor @@ -1,13 +1,8 @@ -@inherits LayoutComponentBase +@inherits MvvmLayoutComponentBase -@inject IJSRuntime JSRuntime -@inject INotificationService _notificationService -@inject Initialization sysinfocusComponentsinit -@inject BrowserExtensions browserExtensions - - - + + @Body @@ -16,42 +11,9 @@ @{ // fixlater: In the future, we should maybe not display raw exceptions to the user - _notificationService.AddNotification(ex.Message, NotificationType.Error); - Console.WriteLine(ex.Message); + _notificationService.AddNotification($"Unhandled Exception: {ex.Message}", NotificationType.Error); } - - -@code { - private ErrorBoundary? errorBoundary; - - - // This runs every time any page is (fully) reloaded (in the browser) - protected override async Task OnInitializedAsync() - { - // var res = await _apiService.DoesAdminUserExist(); - // if (res is false) - // { - // _navManager.NavigateTo("/onboarding"); - // } - // - // await base.OnInitializedAsync(); - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - await browserExtensions.SetToLocalStorage("theme", "dark"); - await sysinfocusComponentsinit.InitializeTheme(); - } - } - - protected override void OnParametersSet() - { - errorBoundary?.Recover(); - } - -} \ No newline at end of file + \ No newline at end of file diff --git a/WardrobeManager.Presentation/Layout/NavMenu.razor b/WardrobeManager.Presentation/Layout/NavMenu.razor index 2c7ed9b..3cad421 100644 --- a/WardrobeManager.Presentation/Layout/NavMenu.razor +++ b/WardrobeManager.Presentation/Layout/NavMenu.razor @@ -3,7 +3,7 @@ @inject NavigationManager Navigation @inject IJSRuntime JsRuntime -@inherits MvvmComponentBase +@inherits MvvmComponentBase