diff --git a/src/iRLeagueManager.Web/Components/DisplayUser.razor b/src/iRLeagueManager.Web/Components/DisplayUser.razor new file mode 100644 index 00000000..1cbcf5a4 --- /dev/null +++ b/src/iRLeagueManager.Web/Components/DisplayUser.razor @@ -0,0 +1,33 @@ +@using iRLeagueApiCore.Common.Models.Users; + +
+ @if (User is null || (string.IsNullOrEmpty(User.Firstname) && string.IsNullOrEmpty(User.Firstname))) + { + + } + else + { + + @if (ShowUserName) + { + + } + } +
+ +@code { + [Parameter] + public string FallbackName { get; set; } = string.Empty; + [Parameter] + public UserModel? User { get; set; } + [Parameter] + public bool ShowUserName { get; set; } = true; + + protected override void OnParametersSet() + { + if (string.IsNullOrEmpty(FallbackName) && User is not null) + { + FallbackName = User.UserName; + } + } +} diff --git a/src/iRLeagueManager.Web/Components/Reviews/ReviewCard.razor b/src/iRLeagueManager.Web/Components/Reviews/ReviewCard.razor index 25e3f752..f6f6a45d 100644 --- a/src/iRLeagueManager.Web/Components/Reviews/ReviewCard.razor +++ b/src/iRLeagueManager.Web/Components/Reviews/ReviewCard.razor @@ -4,6 +4,7 @@ @using Blazored.Typeahead @using iRLeagueApiCore.Common.Models @using iRLeagueApiCore.Common.Models.Reviews +@using iRLeagueApiCore.Common.Models.Users; @using iRLeagueManager.Web.ViewModels @inject NavigationManager NavigationManager diff --git a/src/iRLeagueManager.Web/Components/Reviews/ReviewComment.razor b/src/iRLeagueManager.Web/Components/Reviews/ReviewComment.razor index ab8aa187..8a6a1911 100644 --- a/src/iRLeagueManager.Web/Components/Reviews/ReviewComment.razor +++ b/src/iRLeagueManager.Web/Components/Reviews/ReviewComment.razor @@ -1,11 +1,12 @@ @inherits MvvmComponentBase @using iRLeagueApiCore.Common.Models @using iRLeagueApiCore.Common.Models.Reviews +@using iRLeagueApiCore.Common.Models.Users; @using iRLeagueManager.Web.ViewModels
-
@Bind(Comment, x => x.AuthorName)
+
@Bind(Comment, x=> x.Text)
@@ -36,6 +37,8 @@ public IModalService ModalService { get; set; } = default!; [CascadingParameter] public IEnumerable InvolvedMembers { get; set; } = Array.Empty(); + [CascadingParameter] + public IEnumerable LeagueUsers { get; set; } = Array.Empty(); [Parameter, EditorRequired] public ReviewCommentViewModel Comment { get; set; } = default!; [CascadingParameter] @@ -101,4 +104,9 @@ await OnStateHasChanged.InvokeAsync(); } } + + private UserModel? GetUser(string userId) + { + return LeagueUsers.FirstOrDefault(x => x.UserId == userId); + } } diff --git a/src/iRLeagueManager.Web/Components/Settings/SearchUserModal.razor b/src/iRLeagueManager.Web/Components/Settings/SearchUserModal.razor index 4124e050..3cad8af2 100644 --- a/src/iRLeagueManager.Web/Components/Settings/SearchUserModal.razor +++ b/src/iRLeagueManager.Web/Components/Settings/SearchUserModal.razor @@ -10,10 +10,10 @@ Debounce=500 placeholder="Type Name ..."> - @user?.UserName + - @user.UserName (@user.Firstname @user.Lastname) + + @if (Success) + { +

+ Your email has been successfully verified. + You can now log into your account with your username and password +

+ + Login + + } + else if (Error) + { +

Oops, something went wrong! Please contact the administrator if you continue having problems.

+ } + else + { +

+ Processing email confirmation ... +

+ } +
+ +@code { + [Parameter] + public string UserId { get; set; } = default!; + [Parameter] + public string ConfirmationToken { get; set; } = default!; + + private bool Success { get; set; } = false; + private bool Error { get; set; } = false; + + protected override void OnParametersSet() + { + base.OnParametersSet(); + BlazorParameterNullException.ThrowIfNull(this, UserId); + BlazorParameterNullException.ThrowIfNull(this, ConfirmationToken); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + if (firstRender == false) + { + return; + } + + var result = await ApiService.Client + .Users() + .WithId(UserId) + .ConfirmEmail(ConfirmationToken) + .Post(); + if (result.Success) + { + Success = true; + await InvokeAsync(StateHasChanged); + await Task.Delay(3000); + NavigationManager.NavigateTo("/member/login"); + } + else + { + Error = true; + } + await InvokeAsync(StateHasChanged); + } +} diff --git a/src/iRLeagueManager.Web/Pages/Member/Login.razor b/src/iRLeagueManager.Web/Pages/Member/Login.razor index 1e42645b..2b1f711b 100644 --- a/src/iRLeagueManager.Web/Pages/Member/Login.razor +++ b/src/iRLeagueManager.Web/Pages/Member/Login.razor @@ -2,6 +2,7 @@ @using System.ComponentModel.DataAnnotations @using iRLeagueApiCore.Client @using iRLeagueApiCore.Client.Http +@using iRLeagueApiCore.Common.Responses; @using iRLeagueManager.Web.Extensions @inject IConfiguration configuration @inject ITokenStore tokenStore @@ -26,7 +27,12 @@
- - Forgot password +
+ Forgot password +
+
+ Not a member yet? +
+ @**@ @@ -51,7 +65,9 @@ private bool loading; public bool Loading { get => loading; set { loading = value; InvokeAsync(StateHasChanged); } } - private StatusResultValidator ResultValidator { get; set; } = default!; + private bool ResendConfirmation { get; set; } = false; + + private StatusResultValidator? ResultValidator { get; set; } public User model = new User(); @@ -85,10 +101,15 @@ try { Loading = true; + ResendConfirmation = false; var result = await ApiClient.LogIn(model.Username, model.Password); if (result.Success == false) { - ResultValidator.ValidateResult(result.ToStatusResult()); + ResultValidator?.ValidateResult(result.ToStatusResult()); + if (result.ToStatusResult().Message == "MailConfirm") + { + ResendConfirmation = true; + } return; } var returnUrl = NavigationManager.QueryString("returnUrl") ?? ""; diff --git a/src/iRLeagueManager.Web/Pages/Member/PasswordReset.razor b/src/iRLeagueManager.Web/Pages/Member/PasswordReset.razor index 39178a59..89805379 100644 --- a/src/iRLeagueManager.Web/Pages/Member/PasswordReset.razor +++ b/src/iRLeagueManager.Web/Pages/Member/PasswordReset.razor @@ -39,6 +39,7 @@ } Set Password + @@ -55,7 +56,7 @@ private bool Success { get; set; } - private StatusResultValidator ResultValidator { get; set; } = default!; + private StatusResultValidator? ResultValidator { get; set; } public record SetPassword { @@ -81,12 +82,12 @@ Loading = true; var token = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(ResetToken)); var requestUrl = $"Authenticate/SetPassword/{UserId}"; - var request = ApiService.Client.CustomEndpoint(requestUrl) + var request = ApiService.Client.CustomEndpoint(requestUrl) .Post(new { PasswordToken = token, NewPassword = model.NewPassword}); var result = await request; if (result.Success == false) { - ResultValidator.ValidateResult(result.ToStatusResult()); + ResultValidator?.ValidateResult(result.ToStatusResult()); return; } Success = true; diff --git a/src/iRLeagueManager.Web/Pages/Member/Profile.razor b/src/iRLeagueManager.Web/Pages/Member/Profile.razor new file mode 100644 index 00000000..997012a4 --- /dev/null +++ b/src/iRLeagueManager.Web/Pages/Member/Profile.razor @@ -0,0 +1,90 @@ +@page "/member/{UserId}/profile" +@inject UserViewModel User; +@using iRLeagueApiCore.Common.Models.Users; + +
+
+
+ +
+
+ + + +
+ +
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
+
+ + + +
+ @if (User.HasChanged) + { +
+ + +
+ } +
+
+
+
+
+
+
+ +@code { + [Parameter] + public string UserId { get; set; } = default!; + + private StatusResultValidator? ResultValidator { get; set; } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + if (firstRender == false) + { + return; + } + await User.LoadUser(UserId); + await InvokeAsync(StateHasChanged); + } + + private async Task SaveChanges() + { + var result = await User.SaveChangesAsync(); + if (result.IsSuccess == false) + { + ResultValidator?.ValidateResult(result); + } + } +} diff --git a/src/iRLeagueManager.Web/Pages/Member/Register.razor b/src/iRLeagueManager.Web/Pages/Member/Register.razor new file mode 100644 index 00000000..a2685b59 --- /dev/null +++ b/src/iRLeagueManager.Web/Pages/Member/Register.razor @@ -0,0 +1,146 @@ +@page "/member/Register" +@inject LeagueApiService ApiService +@inject NavigationManager NavigationManager +@using System.ComponentModel.DataAnnotations; +@using iRLeagueApiCore.Client.Endpoints; + +
+ @if (Success) + { +
+

Thank you for your registration!

+

+ An email with a confirmation link has been send to the given email address. Please make sure to check your spam folder if you cannot find any mail from us.
+ Please click the link in the email to finish up your registration and activate your account. +

+
+ } + else + { +
+
+ +
+
+ + + +
+ + + +
+
+
+ + + +
+
+ + + +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
+
+
+
+ } +
+ +@code { + private StatusResultValidator? ResultValidator { get; set; } + + private bool Loading { get; set; } = false; + + private bool Success { get; set; } = false; + + private class RegisterModel : iRLeagueApiCore.Common.Models.Users.RegisterModel + { + [Required] + [MinLength(4)] + [RegularExpression("^[a-zA-Z0-9_-]{4,}$", ErrorMessage = "Username can only contain following characters: \"a-zA-Z0-9_-\"")] + public new string Username { get => base.Username; set => base.Username = value; } + + [Required] + [RegularExpression("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])?(?=.*[#$^+=!*()@%&]).{8,255}$", ErrorMessage = @"Password must be at leas 8 characters long, contain upper and lowercase letters and at least one special character (#$^+=!*()@%&)")] + public new string Password { get => base.Password; set => base.Password = value; } + + [Required(ErrorMessage = "Password confirmation is required")] + [Compare(nameof(Password), ErrorMessage = "Confirmed passsword does not match")] + public string PasswordConfirm { get; set; } = string.Empty; + + [Required] + [Range(typeof(bool), "true", "true", ErrorMessage = "Please accept the privacy policy")] + public bool PrivacyPolicyAgreeed { get; set; } = false; + + [Required] + [Range(typeof(bool), "true", "true", ErrorMessage = "Please accept the terms and conditions")] + public bool TermsConditionsAgreed { get; set; } = false; + } + + private RegisterModel Model { get; set; } = new(); + + private async Task Submit() + { + try + { + Loading = true; + Success = false; + var linkTemplate = $$"""{{NavigationManager.BaseUri}}member/{userId}/confirm/{token}"""; + var result = await ApiService.Client + .Authenticate() + .Register() + .AddQueryParameter(x => x.Add("linkTemplate", linkTemplate)) + .Post(Model); + if (result.Success) + { + Success = true; + } + ResultValidator?.ValidateResult(result.ToStatusResult()); + } + finally + { + Loading = false; + } + } +} diff --git a/src/iRLeagueManager.Web/Pages/Member/RequestPasswordReset.razor b/src/iRLeagueManager.Web/Pages/Member/RequestPasswordReset.razor index 0781e5cb..ccffb069 100644 --- a/src/iRLeagueManager.Web/Pages/Member/RequestPasswordReset.razor +++ b/src/iRLeagueManager.Web/Pages/Member/RequestPasswordReset.razor @@ -70,7 +70,7 @@ Loading = true; model.LinkUriTemplate = GenerateLinkUrlTemplate(); var requestUrl = $"Authenticate/ResetPassword"; - var request = ApiService.Client.CustomEndpoint(requestUrl) + var request = ApiService.Client.CustomEndpoint(requestUrl) .Post(model); var result = await request; if (result.Success == false) diff --git a/src/iRLeagueManager.Web/Pages/Member/ResendConfirmation.razor b/src/iRLeagueManager.Web/Pages/Member/ResendConfirmation.razor new file mode 100644 index 00000000..cade0f92 --- /dev/null +++ b/src/iRLeagueManager.Web/Pages/Member/ResendConfirmation.razor @@ -0,0 +1,82 @@ +@page "/member/resend-confirmation" +@inject LeagueApiService ApiService +@inject NavigationManager NavigationManager +@using System.ComponentModel.DataAnnotations; +@using iRLeagueApiCore.Client.Endpoints; + +
+ @if (Success) + { +
+

An email with a confirmation link has been send to the given email address. Please make sure to check your spam folder if you cannot find any mail from us.

+

If you continue having problems with this step please contact simon@iRLeagueManager.net

+
+ } + else + { +
+
+ +
+
+ + + +
+ + + +
+ +
+
+
+ } +
+ +@code { + private sealed class ConfirmationContext + { + [Required] + [EmailAddress] + public string Email { get; set; } = string.Empty; + } + + private StatusResultValidator? ResultValidator { get; set; } + + private bool Loading { get; set; } = false; + + private bool Success { get; set; } = false; + + private ConfirmationContext Context { get; } = new(); + + private async Task Submit() + { + try + { + Loading = true; + var linkTemplate = $$"""{{NavigationManager.BaseUri}}member/{userId}/confirm/{token}"""; + var result = await ApiService.Client + .Users() + .ResendConfirmation() + .AddQueryParameter(x => x.Add("linkTemplate", linkTemplate)) + .Post(Context.Email); + if (result.Success == false) + { + ResultValidator?.ValidateResult(result.ToStatusResult()); + return; + } + Success = true; + } + finally + { + Loading = false; + } + } +} diff --git a/src/iRLeagueManager.Web/Pages/Reviews.razor b/src/iRLeagueManager.Web/Pages/Reviews.razor index 46c4fbc3..7bb07eec 100644 --- a/src/iRLeagueManager.Web/Pages/Reviews.razor +++ b/src/iRLeagueManager.Web/Pages/Reviews.razor @@ -35,6 +35,7 @@ + @switch (SelectedTabIndex) { case 0: @@ -124,6 +125,7 @@ break; } + @code { @@ -178,6 +180,7 @@ { return; } + await ReviewsVm.LoadUsers(); SelectedTabIndex = NavigationManager.QueryParameter(tabIndexParam); SelectedReviewId = NavigationManager.QueryParameter(reviewIdParam); } diff --git a/src/iRLeagueManager.Web/Pages/Settings/UserSettings.razor b/src/iRLeagueManager.Web/Pages/Settings/UserSettings.razor index 044e4506..74b9de40 100644 --- a/src/iRLeagueManager.Web/Pages/Settings/UserSettings.razor +++ b/src/iRLeagueManager.Web/Pages/Settings/UserSettings.razor @@ -30,14 +30,9 @@ @foreach(var user in @Bind(Users, x => x.LeagueUsers)) { - @if (string.IsNullOrWhiteSpace(user.FirstName) && string.IsNullOrWhiteSpace(user.LastName)) - { - @user.UserName - } - else - { - @user.FirstName @user.LastName - } + + + @foreach(var role in LeagueRoles.RolesAvailable) { diff --git a/src/iRLeagueManager.Web/Program.cs b/src/iRLeagueManager.Web/Program.cs index 4ffb7213..4ec14cf5 100644 --- a/src/iRLeagueManager.Web/Program.cs +++ b/src/iRLeagueManager.Web/Program.cs @@ -12,6 +12,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Reflection; +using Microsoft.AspNetCore.Authorization; var builder = WebApplication.CreateBuilder(args); @@ -46,6 +47,11 @@ builder.Services.AddScoped(); builder.Services.AddTrackList(); builder.Services.AddViewModels(); +builder.Services.AddSingleton(); +builder.Services.AddAuthorization(config => +{ + config.AddPolicy(ProfileOwnerRequirement.Policy, policy => policy.AddRequirements(new ProfileOwnerRequirement())); +}); builder.Services.AddBlazoredModal(); builder.Services.AddLocalization(); diff --git a/src/iRLeagueManager.Web/Services.cs b/src/iRLeagueManager.Web/Services.cs index 18fce473..d3575219 100644 --- a/src/iRLeagueManager.Web/Services.cs +++ b/src/iRLeagueManager.Web/Services.cs @@ -31,6 +31,7 @@ public static IServiceCollection AddViewModels(this IServiceCollection services) services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); + services.TryAddTransient(); services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); diff --git a/src/iRLeagueManager.Web/Shared/Header.razor b/src/iRLeagueManager.Web/Shared/Header.razor index cca95e89..d7a886ba 100644 --- a/src/iRLeagueManager.Web/Shared/Header.razor +++ b/src/iRLeagueManager.Web/Shared/Header.razor @@ -101,17 +101,18 @@ diff --git a/src/iRLeagueManager.Web/Shared/ProfileHandler.cs b/src/iRLeagueManager.Web/Shared/ProfileHandler.cs new file mode 100644 index 00000000..8f79e3df --- /dev/null +++ b/src/iRLeagueManager.Web/Shared/ProfileHandler.cs @@ -0,0 +1,33 @@ +using Humanizer; +using Microsoft.AspNetCore.Authorization; +using static Humanizer.In; +using System.Security.Claims; +using iRLeagueManager.Web.Extensions; + +namespace iRLeagueManager.Web.Shared; + +public class ProfileHandler : AuthorizationHandler +{ + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ProfileOwnerRequirement requirement) + { + if (context.User.Identity?.IsAuthenticated == true) + { + var profileId = context.Resource?.ToString() ?? string.Empty; + + if (IsOwner(context.User, profileId)) + { + context.Succeed(requirement); + } + } + + return Task.CompletedTask; + } + + private static bool IsOwner(ClaimsPrincipal user, string profileId) + { + var userProfileId = user.GetUserId(); + + return userProfileId == profileId; + } +} + diff --git a/src/iRLeagueManager.Web/Shared/ProfileOwnerRequirement.cs b/src/iRLeagueManager.Web/Shared/ProfileOwnerRequirement.cs new file mode 100644 index 00000000..7707e25b --- /dev/null +++ b/src/iRLeagueManager.Web/Shared/ProfileOwnerRequirement.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Authorization; + +namespace iRLeagueManager.Web.Shared; + +public class ProfileOwnerRequirement : IAuthorizationRequirement +{ + public const string Policy = "UserIsOwner"; + public ProfileOwnerRequirement() { } + +} \ No newline at end of file diff --git a/src/iRLeagueManager.Web/Shared/StatusResultValidator.cs b/src/iRLeagueManager.Web/Shared/StatusResultValidator.cs index ddfd1b3a..c182da4a 100644 --- a/src/iRLeagueManager.Web/Shared/StatusResultValidator.cs +++ b/src/iRLeagueManager.Web/Shared/StatusResultValidator.cs @@ -46,7 +46,7 @@ public void ValidateResult(StatusResult result) switch (result.Status) { case StatusResult.Unauthorized: - AddUnauthorizedValidationMessages(); + AddUnauthorizedValidationMessages(result); break; case StatusResult.BadRequest: AddBadRequestValidationMessages(result); @@ -73,8 +73,14 @@ private void DisplayErrorMessage(StatusResult result) } } - private void AddUnauthorizedValidationMessages() + private void AddUnauthorizedValidationMessages(StatusResult result) { + if (result.Message == "MailConfirm") + { + ErrorMessage = "Email confirmation is missing"; + return; + } + var usernameField = CurrentEditContext.Field("Username"); var passwordField = CurrentEditContext.Field("Password"); messageStore.Add(usernameField, ""); diff --git a/src/iRLeagueManager.Web/ViewModels/LeagueViewModelBase.cs b/src/iRLeagueManager.Web/ViewModels/LeagueViewModelBase.cs index ceb3524f..60abc591 100644 --- a/src/iRLeagueManager.Web/ViewModels/LeagueViewModelBase.cs +++ b/src/iRLeagueManager.Web/ViewModels/LeagueViewModelBase.cs @@ -112,6 +112,7 @@ public virtual TModel GetModel() public virtual void SetModel(TModel model) { this.model = model; + HasChanged = false; } public virtual TModel CopyModel() diff --git a/src/iRLeagueManager.Web/ViewModels/ReviewsPageViewModel.cs b/src/iRLeagueManager.Web/ViewModels/ReviewsPageViewModel.cs index cec8173b..952f48ed 100644 --- a/src/iRLeagueManager.Web/ViewModels/ReviewsPageViewModel.cs +++ b/src/iRLeagueManager.Web/ViewModels/ReviewsPageViewModel.cs @@ -1,5 +1,6 @@ using iRLeagueApiCore.Common.Enums; using iRLeagueApiCore.Common.Models; +using iRLeagueApiCore.Common.Models.Users; using iRLeagueManager.Web.Data; using iRLeagueManager.Web.Extensions; @@ -30,6 +31,9 @@ public ReviewsPageViewModel(ILoggerFactory loggerFactory, LeagueApiService apiSe private IEnumerable eventMembers = Array.Empty(); public IEnumerable EventMembers { get => eventMembers; set => Set(ref eventMembers, value); } + private ObservableCollection leagueUsers = new(); + public ObservableCollection LeagueUsers { get => leagueUsers; set => Set(ref leagueUsers, value); } + public async Task LoadFromEventAsync(long eventId, CancellationToken cancellationToken = default) { if (ApiService.CurrentLeague == null) @@ -101,6 +105,30 @@ public async Task LoadFromEventAsync(long eventId, CancellationToken cancellatio } } + public async Task LoadUsers(CancellationToken cancellationToken = default) + { + if (CurrentLeague is null) + { + return LeagueNullResult(); + } + + try + { + Loading = true; + var result = await CurrentLeague.Users() + .Get(cancellationToken); + if (result.Success && result.Content is not null) + { + LeagueUsers = new(result.Content); + } + return result.ToStatusResult(); + } + finally + { + Loading = false; + } + } + public async Task FileProtest(long sessionId, PostProtestModel protest, CancellationToken cancellationToken = default) { if (ApiService.CurrentLeague is null) diff --git a/src/iRLeagueManager.Web/ViewModels/UserViewModel.cs b/src/iRLeagueManager.Web/ViewModels/UserViewModel.cs new file mode 100644 index 00000000..0e426211 --- /dev/null +++ b/src/iRLeagueManager.Web/ViewModels/UserViewModel.cs @@ -0,0 +1,77 @@ +using iRLeagueApiCore.Common.Models.Users; +using iRLeagueManager.Web.Data; +using iRLeagueManager.Web.Extensions; + +namespace iRLeagueManager.Web.ViewModels; + +public class UserViewModel : LeagueViewModelBase +{ + public UserViewModel(ILoggerFactory loggerFactory, LeagueApiService apiService) : + this(loggerFactory, apiService, new()) + { + } + + public UserViewModel(ILoggerFactory loggerFactory, LeagueApiService apiService, PrivateUserModel model) : + base(loggerFactory, apiService, model) + { + } + + public string UserId => model.UserId; + public string UserName => model.UserName; + public string Firstname { get => model.Firstname; set => SetP(model.Firstname, value => model.Firstname = value, value); } + public string Lastname { get => model.Lastname; set => SetP(model.Lastname, value => model.Lastname = value, value); } + public string Email { get => model.Email; set => SetP(model.Email, value => model.Email = value, value); } + public bool ShowFullname { get => !model.HideFirstnameLastname; set => SetP(!model.HideFirstnameLastname, value => model.HideFirstnameLastname = !value, value); } + + public async Task LoadUser(string userId, CancellationToken cancellationToken = default) + { + try + { + Loading = true; + var result = await ApiService.Client + .CustomEndpoint($"Users/{userId}") + .Get(cancellationToken); + if (result.Success && result.Content is not null) + { + SetModel(result.Content); + } + return result.ToStatusResult(); + } + finally + { + Loading = false; + } + } + + public async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + try + { + Loading = true; + var result = await ApiService.Client + .Users() + .WithId(UserId) + .Put(MapToPutUserModel(model), cancellationToken); + if (result.Success && result.Content is not null) + { + SetModel(result.Content); + } + return result.ToStatusResult(); + } + finally + { + Loading = false; + } + } + + private PutUserModel MapToPutUserModel(PrivateUserModel user) + { + return new() + { + Firstname = user.Firstname, + Lastname = user.Lastname, + Email = user.Email, + HideFirstnameLastname = user.HideFirstnameLastname + }; + } +} diff --git a/src/iRLeagueManager.Web/iRLeagueManager.Web.csproj b/src/iRLeagueManager.Web/iRLeagueManager.Web.csproj index 55d4d07f..dd0b5dd2 100644 --- a/src/iRLeagueManager.Web/iRLeagueManager.Web.csproj +++ b/src/iRLeagueManager.Web/iRLeagueManager.Web.csproj @@ -2,11 +2,12 @@ net6.0 - 0.5.3 + 0.6.0 enable enable aspnet-iRLeagueManager.Web-2B05F9DC-55A3-49D1-BD64-31507000EDF3 Debug;Release; + 11 @@ -130,8 +131,8 @@ - - + + diff --git a/src/iRLeagueManager.Web/wwwroot/css/site.css b/src/iRLeagueManager.Web/wwwroot/css/site.css index 6c1d0cc8..eee19cc0 100644 --- a/src/iRLeagueManager.Web/wwwroot/css/site.css +++ b/src/iRLeagueManager.Web/wwwroot/css/site.css @@ -22,6 +22,10 @@ background: var(--bs-form-bg); } + .form-control:disabled { + background: var(--bs-body-bg) + } + .form-select { background: var(--bs-form-bg); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");