diff --git a/Linguard/Web/Pages/Account.razor b/Linguard/Web/Pages/Account.razor new file mode 100644 index 0000000..53dcb92 --- /dev/null +++ b/Linguard/Web/Pages/Account.razor @@ -0,0 +1,177 @@ +@page "/account" +@using Linguard.Core.Utils +@using Microsoft.AspNetCore.Components +@using Microsoft.AspNetCore.Identity +@using System.Security.Claims +@using Linguard.Web.Services + +@($"{AssemblyInfo.Product} | {Title}") + + + + + + + + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +@inject NotificationService _notificationService +@inject ILogger _logger +@inject AuthenticationStateProvider _authenticationStateProvider +@inject UserManager _userManager +@inject IStateHasChangedNotifierService _notifier + +@code { + private const string Title = "Account"; + private IdentityUser _user; + private RadzenPassword _currentPassword; + private RadzenPassword _newPassword; + private RadzenPassword _confirmNewPassword; + + + protected override async Task OnInitializedAsync() { + var authState = await _authenticationStateProvider.GetAuthenticationStateAsync(); + var userId = authState.User.Claims.SingleOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier)); + _user = await _userManager.FindByIdAsync(userId?.Value); + } + + private async Task Save() { + try { + await _userManager.UpdateAsync(_user); + _notificationService.Notify(new NotificationMessage { + Severity = NotificationSeverity.Success, + Summary = "Account updated!" + }); + _notifier.Notify(); + } + catch (Exception e) { + _notificationService.Notify(new NotificationMessage { + Severity = NotificationSeverity.Error, + Summary = "Failed to update account", + Detail = e.Message + }); + } + } + + private async Task ChangePassword() { + var result = await IsPasswordValid(); + if (!result) return; + try { + await _userManager.ChangePasswordAsync(_user, _currentPassword.Value, _newPassword.Value); + // TODO not working + // _currentPassword.Value = string.Empty; + // _newPassword.Value = string.Empty; + // _confirmNewPassword.Value = string.Empty; + _notificationService.Notify(new NotificationMessage { + Severity = NotificationSeverity.Success, + Summary = "Password updated!" + }); + StateHasChanged(); + } + catch (Exception e) { + _notificationService.Notify(new NotificationMessage { + Severity = NotificationSeverity.Error, + Summary = "Unable to update password!", + Detail = e.Message + }); + } + } + + private async Task IsPasswordValid() { + var passwordOk = !string.IsNullOrEmpty(_currentPassword.Value) + && await _userManager.CheckPasswordAsync(_user, _currentPassword.Value); + if (!passwordOk) { + _notificationService.Notify(new NotificationMessage { + Severity = NotificationSeverity.Error, + Summary = "Unable to update password!", + Detail = "Current password is not correct." + }); + return await Task.FromResult(false); + } + if (string.IsNullOrEmpty(_newPassword.Value) || string.IsNullOrEmpty(_confirmNewPassword.Value)) { + _notificationService.Notify(new NotificationMessage { + Severity = NotificationSeverity.Error, + Summary = "Unable to update password!", + Detail = "Password cannot be empty." + }); + return await Task.FromResult(false); + } + if (!_newPassword.Value.Equals(_confirmNewPassword.Value)) { + _notificationService.Notify(new NotificationMessage { + Severity = NotificationSeverity.Error, + Summary = "Unable to update password!", + Detail = "Passwords do not match." + }); + return await Task.FromResult(false); + } + return await Task.FromResult(true); + } + +} diff --git a/Linguard/Web/Pages/Settings.razor b/Linguard/Web/Pages/Settings.razor index e57e239..ab78056 100644 --- a/Linguard/Web/Pages/Settings.razor +++ b/Linguard/Web/Pages/Settings.razor @@ -26,14 +26,14 @@
-
- - -
+
+ +
+
diff --git a/Linguard/Web/Pages/Signup.razor b/Linguard/Web/Pages/Signup.razor index c0dae23..3e81ff9 100644 --- a/Linguard/Web/Pages/Signup.razor +++ b/Linguard/Web/Pages/Signup.razor @@ -9,7 +9,7 @@ @($"{AssemblyInfo.Product} | Sign up") - +
diff --git a/Linguard/Web/Program.cs b/Linguard/Web/Program.cs index 4b80f9e..1d9e01e 100644 --- a/Linguard/Web/Program.cs +++ b/Linguard/Web/Program.cs @@ -42,6 +42,7 @@ #region Web services builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); @@ -61,7 +62,7 @@ #region Authentication builder.Services.AddDbContext(); -builder.Services.AddTransient(); +builder.Services.AddScoped(); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddIdentityCore(options => { options.SignIn.RequireConfirmedAccount = false; @@ -69,6 +70,7 @@ options.Password.RequiredLength = 1; options.Password.RequireUppercase = false; options.Password.RequireNonAlphanumeric = false; + options.Password.RequireLowercase = false; }) .AddRoles() .AddEntityFrameworkStores() diff --git a/Linguard/Web/Services/IStateHasChangedNotifierService.cs b/Linguard/Web/Services/IStateHasChangedNotifierService.cs new file mode 100644 index 0000000..ce6ae13 --- /dev/null +++ b/Linguard/Web/Services/IStateHasChangedNotifierService.cs @@ -0,0 +1,7 @@ +namespace Linguard.Web.Services; + +public interface IStateHasChangedNotifierService { + void Subscribe(EventHandler handler); + void UnSubscribe(EventHandler handler); + void Notify(); +} \ No newline at end of file diff --git a/Linguard/Web/Services/StateHasChangedNotifierService.cs b/Linguard/Web/Services/StateHasChangedNotifierService.cs new file mode 100644 index 0000000..e858e31 --- /dev/null +++ b/Linguard/Web/Services/StateHasChangedNotifierService.cs @@ -0,0 +1,18 @@ +namespace Linguard.Web.Services; + +public class StateHasChangedNotifierService : IStateHasChangedNotifierService { + private readonly ISet _handlers = new HashSet(); + public void Subscribe(EventHandler handler) { + _handlers.Add(handler); + } + + public void UnSubscribe(EventHandler handler) { + _handlers.Remove(handler); + } + + public void Notify() { + foreach (var handler in _handlers) { + handler.Invoke(this, EventArgs.Empty); + } + } +} \ No newline at end of file diff --git a/Linguard/Web/Shared/ProfileMenu.razor b/Linguard/Web/Shared/ProfileMenu.razor index 73d0ff1..e31fa59 100644 --- a/Linguard/Web/Shared/ProfileMenu.razor +++ b/Linguard/Web/Shared/ProfileMenu.razor @@ -27,12 +27,14 @@ @inject AuthenticationStateProvider _authenticationStateProvider @inject IJSRuntime _jsRuntime @inject IAuthenticationService _authenticationService +@inject UserManager _userManager +@inject IStateHasChangedNotifierService _notifier @code { - string? _user; + IdentityUser? _user; string? _email; - string GreetingMessage => $"Hi, {_user}"; + string GreetingMessage => $"Hi, {_user?.UserName}"; string GravatarEmail => _email ?? "user@example.com"; async Task OnStyleChanged(string value) { @@ -44,8 +46,12 @@ protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - var state = await _authenticationStateProvider.GetAuthenticationStateAsync(); - _user = state.User.Identity?.Name; + var authState = await _authenticationStateProvider.GetAuthenticationStateAsync(); + var userId = authState.User.Claims.SingleOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier)); + _user = await _userManager.FindByIdAsync(userId?.Value); + _notifier.Subscribe((_, _) => { + StateHasChanged(); + }); } private void Logout() { diff --git a/Linguard/WebMock/Program.cs b/Linguard/WebMock/Program.cs index 39c1fee..8dfe9af 100644 --- a/Linguard/WebMock/Program.cs +++ b/Linguard/WebMock/Program.cs @@ -64,6 +64,7 @@ #region Web services builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddScoped();