Add user manager page #22
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="login-field">
|
||||
<div class="input-title">@ResourcesKey.EnterPassword</div>
|
||||
<InputText class="input-text"
|
||||
<InputText class="input-text" type="password"
|
||||
@bind-Value="UserDto.Password" />
|
||||
</div>
|
||||
<div class="login-field">
|
||||
|
||||
@@ -6,13 +6,15 @@ namespace GameIdeas.BlazorApp.Pages.UserMenu;
|
||||
public partial class UserMenu
|
||||
{
|
||||
[Inject] private IAuthGateway AuthGateway { get; set; } = default!;
|
||||
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
private bool ContentVisile = false;
|
||||
|
||||
private async Task HandleLogoutClicked()
|
||||
{
|
||||
await AuthGateway.Logout();
|
||||
ContentVisile = false;
|
||||
await AuthGateway.Logout();
|
||||
NavigationManager.NavigateTo("/Games");
|
||||
}
|
||||
|
||||
private void HandleAccountClicked()
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select
|
||||
@using Blazored.FluentValidation
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
|
||||
@using GameIdeas.BlazorApp.Shared.Constants
|
||||
@using GameIdeas.Shared.Dto
|
||||
|
||||
<EditForm EditContext="EditContext" OnSubmit="HandleSubmitClicked">
|
||||
<FluentValidationValidator Validator="Validator" />
|
||||
<div class="row">
|
||||
<div class="icon">
|
||||
@Icons.Account
|
||||
</div>
|
||||
<div class="name">
|
||||
<input type="text" class="input-name" @bind="@User.Username" disabled="@(!CanEdit)">
|
||||
<InputText class="input-name" @bind-Value="@User.Username" disabled="@(!IsEditing)" />
|
||||
</div>
|
||||
<div class="password">
|
||||
<input type="password" class="input-password" placeholder="********" @bind="@User.Password" disabled="@(!CanEdit)">
|
||||
<InputText type="password" class="input-password" placeholder="********" @bind-Value="@User.Password" disabled="@(!IsEditing)" />
|
||||
</div>
|
||||
<div class="role">
|
||||
<Select TItem="RoleDto" THeader="object" DisableClicked=!CanEdit Type="SelectType.Single"
|
||||
Theme="SelectTheme.Single" Values="Roles" ValuesChanged="HandleValuesChanged" Params="SelectRoleParams">
|
||||
<span class="role-label @(!CanEdit ? "disabled" : "")">@(User.Role?.Name ?? ResourcesKey.Unknown)</span>
|
||||
<Select TItem="RoleDto" THeader="object" DisableClicked=!IsEditing Type="SelectType.Single"
|
||||
Theme="SelectTheme.Single" Values="Roles" ValuesChanged="HandleValuesChanged" Params="SelectRoleParams">
|
||||
<span class="role-label @(!IsEditing ? "disabled" : "")">@(User.Role?.Name ?? ResourcesKey.Unknown)</span>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button type="button" class="remove" @onclick="HandleRemoveClicked">@Icons.Bin</button>
|
||||
<button type="button" class="edit @(CanEdit ? "selected" : "")" @onclick="HandleEditClicked">@Icons.Pen</button>
|
||||
<button type="submit" class="submit" style="display: @(CanEdit ? "block" : "none");">@Icons.Check</button>
|
||||
@if (CanEdit)
|
||||
{
|
||||
<button type="button" class="edit @(IsEditing ? "selected" : "")" @onclick="HandleEditClicked">@Icons.Pen</button>
|
||||
}
|
||||
<button type="submit" class="submit" style="display: @(IsEditing ? "block" : "none");">@Icons.Check</button>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
@@ -1,3 +1,4 @@
|
||||
using FluentValidation;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
|
||||
using GameIdeas.Shared.Dto;
|
||||
@@ -10,9 +11,11 @@ public partial class UserRow
|
||||
{
|
||||
[Parameter] public UserDto User { get; set; } = new();
|
||||
[Parameter] public List<RoleDto> Roles { get; set; } = [];
|
||||
[Parameter] public bool CanEdit { get; set; } = false;
|
||||
[Parameter] public bool CanEdit { get; set; } = true;
|
||||
[Parameter] public bool IsEditing { get; set; } = false;
|
||||
[Parameter] public EventCallback<UserDto> OnRemove { get; set; }
|
||||
[Parameter] public EventCallback<UserDto> OnSubmit { get; set; }
|
||||
[Parameter] public IValidator Validator { get; set; } = default!;
|
||||
|
||||
private SelectParams<RoleDto, object> SelectRoleParams = new();
|
||||
private EditContext? EditContext;
|
||||
@@ -53,7 +56,7 @@ public partial class UserRow
|
||||
return;
|
||||
}
|
||||
|
||||
CanEdit = false;
|
||||
IsEditing = false;
|
||||
await OnSubmit.InvokeAsync(User);
|
||||
User.Password = null;
|
||||
}
|
||||
@@ -61,14 +64,14 @@ public partial class UserRow
|
||||
|
||||
private void HandleEditClicked()
|
||||
{
|
||||
if (CanEdit)
|
||||
if (IsEditing)
|
||||
{
|
||||
User.Username = OriginalUser?.Username;
|
||||
User.Role = OriginalUser?.Role;
|
||||
User.Password = null;
|
||||
}
|
||||
|
||||
CanEdit = !CanEdit;
|
||||
IsEditing = !IsEditing;
|
||||
}
|
||||
|
||||
private async Task HandleRemoveClicked()
|
||||
|
||||
@@ -22,11 +22,17 @@
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
input[disabled], input, input::placeholder {
|
||||
::deep .input-name,
|
||||
::deep .input-name[disabled],
|
||||
::deep .input-password,
|
||||
::deep .input-password[disabled],
|
||||
::deep .input-password::placeholder {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
input, .role-label {
|
||||
::deep .input-name,
|
||||
::deep .input-password,
|
||||
.role-label {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
@@ -38,7 +44,9 @@ input, .role-label {
|
||||
background: rgb(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
input[disabled], .disabled {
|
||||
::deep .input-name[disabled],
|
||||
::deep .input-password[disabled],
|
||||
.disabled {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users.Components;
|
||||
|
||||
public class UserValidator : AbstractValidator<UserDto>
|
||||
public class UserCreateValidator : AbstractValidator<UserDto>
|
||||
{
|
||||
public UserValidator()
|
||||
public UserCreateValidator()
|
||||
{
|
||||
RuleFor(user => user.Username)
|
||||
.NotEmpty()
|
||||
@@ -21,3 +21,30 @@ public class UserValidator : AbstractValidator<UserDto>
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
}
|
||||
}
|
||||
|
||||
public class UserUpdateValidator : AbstractValidator<UserDto>
|
||||
{
|
||||
public UserUpdateValidator()
|
||||
{
|
||||
When(user => user.Password == null && user.Role == null, () =>
|
||||
{
|
||||
RuleFor(user => user.Username)
|
||||
.NotEmpty()
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
});
|
||||
|
||||
When(user => user.Username == null && user.Role == null, () =>
|
||||
{
|
||||
RuleFor(user => user.Password)
|
||||
.NotEmpty()
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
});
|
||||
|
||||
When(user => user.Username == null && user.Password == null, () =>
|
||||
{
|
||||
RuleFor(user => user.Role)
|
||||
.NotEmpty()
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ public interface IUserGateway
|
||||
{
|
||||
Task<UserListDto> GetUsers(UserFilterParams filterParams, int currentPage);
|
||||
Task<IEnumerable<RoleDto>> GetRoles();
|
||||
Task<string> CreateUser(UserDto user);
|
||||
Task<string> UpdateUser(UserDto user);
|
||||
Task<string> DeleteUser(string userId);
|
||||
Task<IdDto> CreateUser(UserDto user);
|
||||
Task<IdDto> UpdateUser(UserDto user);
|
||||
Task<IdDto> DeleteUser(string userId);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ namespace GameIdeas.BlazorApp.Pages.Users.Gateways;
|
||||
|
||||
public class UserGateway(IHttpClientService httpClient) : IUserGateway
|
||||
{
|
||||
public async Task<string> CreateUser(UserDto user)
|
||||
public async Task<IdDto> CreateUser(UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.PostAsync<string>(Endpoints.User.Create, user)
|
||||
return await httpClient.PostAsync<IdDto>(Endpoints.User.Create, user)
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorCreateUser);
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -22,11 +22,11 @@ public class UserGateway(IHttpClientService httpClient) : IUserGateway
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> DeleteUser(string userId)
|
||||
public async Task<IdDto> DeleteUser(string userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.DeleteAsync<string>(Endpoints.User.Delete(userId))
|
||||
return await httpClient.DeleteAsync<IdDto>(Endpoints.User.Delete(userId))
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorDeleteUser);
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -69,11 +69,11 @@ public class UserGateway(IHttpClientService httpClient) : IUserGateway
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> UpdateUser(UserDto user)
|
||||
public async Task<IdDto> UpdateUser(UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.PutAsync<string>(Endpoints.User.Update(user.Id ?? string.Empty), user)
|
||||
return await httpClient.PutAsync<IdDto>(Endpoints.User.Update(user.Id ?? string.Empty), user)
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorUpdateUser);
|
||||
}
|
||||
catch (Exception)
|
||||
|
||||
@@ -16,17 +16,21 @@
|
||||
<div class="header-content">
|
||||
<SearchInput Placeholder="@ResourcesKey.EnterUsername" @bind-Text="FilterParams.Name" />
|
||||
<SelectSearch TItem="RoleDto" Placeholder="@ResourcesKey.Roles" @bind-Values="FilterParams.Roles"
|
||||
Items="Roles.ToList()" GetLabel="@(role => role.Name)" Theme="SelectTheme.Filter" />
|
||||
Items="Roles.ToList()" GetLabel="@(role => role.Name)" Theme="SelectTheme.Filter" />
|
||||
</div>
|
||||
</GameHeader>
|
||||
|
||||
<div class="container">
|
||||
<UserRow User="UserAdd" Roles="Roles.ToList()" CanEdit="false" IsEditing="true" OnRemove="HandleResetUser" OnSubmit="HandleSubmitUser" Validator="@(new UserCreateValidator())" />
|
||||
|
||||
<span class="line"></span>
|
||||
|
||||
<div class="content">
|
||||
@if (!IsLoading)
|
||||
{
|
||||
@foreach (var user in UserList.Users ?? [])
|
||||
{
|
||||
<UserRow User="user" Roles="Roles.ToList()" OnRemove="HandleRemoveUser" OnSubmit="HandleSubmitUser" />
|
||||
<UserRow User="user" Roles="Roles.ToList()" OnRemove="HandleRemoveUser" OnSubmit="HandleUpdateUser" Validator="@(new UserUpdateValidator())" />
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -16,6 +16,7 @@ public partial class Users
|
||||
private UserListDto UserList = new();
|
||||
private IEnumerable<RoleDto> Roles = [];
|
||||
private int CurrentPage = 1;
|
||||
private UserDto UserAdd = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -43,12 +44,72 @@ public partial class Users
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
private Task HandleSubmitUser(UserDto args)
|
||||
|
||||
private async Task HandleSubmitUser(UserDto user)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
await UserGateway.CreateUser(user);
|
||||
await FetchData(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
|
||||
UserAdd = new();
|
||||
}
|
||||
private Task HandleRemoveUser(UserDto args)
|
||||
|
||||
private async Task HandleUpdateUser(UserDto user)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
await UserGateway.UpdateUser(user);
|
||||
await FetchData(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleRemoveUser(UserDto user)
|
||||
{
|
||||
if (user.Id == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
await UserGateway.DeleteUser(user.Id);
|
||||
await FetchData(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
private void HandleResetUser(UserDto args)
|
||||
{
|
||||
UserAdd = new();
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,19 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
::deep .search-container .select-container {
|
||||
::deep .search-container, ::deep .select-container {
|
||||
box-sizing: border-box;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 20px 200px;
|
||||
display: grid;
|
||||
grid-gap: 20px;
|
||||
}
|
||||
|
||||
.line {
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -7,7 +7,9 @@ public class GlobalConstants
|
||||
public readonly static Guid MEMBER_ID = Guid.Parse("{BCE14DEA-1748-4A76-8485-ADEE83DF5EFD}");
|
||||
|
||||
public const string ADMINISTRATOR = "Administrateur";
|
||||
public const string ADMINISTRATOR_NORMALIZED = "ADMINISTRATEUR";
|
||||
public const string MEMBER = "Membre";
|
||||
public const string MEMBER_NORMALIZED = "MEMBRE";
|
||||
public const string ADMIN_MEMBER = $"{ADMINISTRATOR}, {MEMBER}";
|
||||
|
||||
public const int JWT_DURATION_HOUR = 12;
|
||||
|
||||
6
src/GameIdeas/GameIdeas.Shared/Dto/IdDto.cs
Normal file
6
src/GameIdeas/GameIdeas.Shared/Dto/IdDto.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace GameIdeas.Shared.Dto;
|
||||
|
||||
public class IdDto
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
}
|
||||
@@ -72,11 +72,12 @@ public class UserController(
|
||||
|
||||
[Authorize(Roles = GlobalConstants.ADMINISTRATOR)]
|
||||
[HttpPost("Create")]
|
||||
public async Task<ActionResult<string>> CreateUser([FromBody] UserDto user)
|
||||
public async Task<ActionResult<IdDto>> CreateUser([FromBody] UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Created("/Create", await userWriteService.CreateUser(user));
|
||||
var id = new IdDto() { Id = await userWriteService.CreateUser(user) };
|
||||
return Created("/Create", id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -87,11 +88,12 @@ public class UserController(
|
||||
|
||||
[Authorize(Roles = GlobalConstants.ADMINISTRATOR)]
|
||||
[HttpPut("Update/{userId}")]
|
||||
public async Task<ActionResult<string>> UpdateUser(string userId, [FromBody] UserDto user)
|
||||
public async Task<ActionResult<IdDto>> UpdateUser(string userId, [FromBody] UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Created("/Update", await userWriteService.UpdateUser(userId, user));
|
||||
var id = new IdDto() { Id = await userWriteService.UpdateUser(userId, user) };
|
||||
return Created("/Update", id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -102,11 +104,12 @@ public class UserController(
|
||||
|
||||
[Authorize(Roles = GlobalConstants.ADMINISTRATOR)]
|
||||
[HttpDelete("Delete/{userId}")]
|
||||
public async Task<ActionResult<string>> DeleteUser(string userId)
|
||||
public async Task<ActionResult<IdDto>> DeleteUser(string userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Created("/Delete", await userWriteService.DeleteUser(userId));
|
||||
var id = new IdDto() { Id = await userWriteService.DeleteUser(userId) };
|
||||
return Created("/Delete", id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -19,13 +19,13 @@ namespace GameIdeas.WebAPI.Migrations
|
||||
{
|
||||
GlobalConstants.ADMINISTRATOR_ID.ToString(),
|
||||
GlobalConstants.ADMINISTRATOR,
|
||||
GlobalConstants.ADMINISTRATOR.Normalize(),
|
||||
GlobalConstants.ADMINISTRATOR_NORMALIZED,
|
||||
Guid.NewGuid().ToString()
|
||||
},
|
||||
{
|
||||
GlobalConstants.MEMBER_ID.ToString(),
|
||||
GlobalConstants.MEMBER,
|
||||
GlobalConstants.MEMBER.Normalize(),
|
||||
GlobalConstants.MEMBER_NORMALIZED,
|
||||
Guid.NewGuid().ToString()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -59,6 +59,17 @@ services.AddAuthentication(options =>
|
||||
};
|
||||
});
|
||||
|
||||
services.Configure<IdentityOptions>(options =>
|
||||
{
|
||||
// Default Password settings.
|
||||
options.Password.RequireDigit = false;
|
||||
options.Password.RequireLowercase = false;
|
||||
options.Password.RequireNonAlphanumeric = false;
|
||||
options.Password.RequireUppercase = false;
|
||||
options.Password.RequiredLength = 6;
|
||||
options.Password.RequiredUniqueChars = 1;
|
||||
});
|
||||
|
||||
services.AddAuthorization();
|
||||
|
||||
services.AddSingleton<TranslationService>();
|
||||
|
||||
@@ -29,7 +29,7 @@ public class UserWriteService(
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UserInvalidException(string.Join("; ", result.Errors));
|
||||
throw new UserInvalidException(string.Join("; ", result.Errors.Select(e => $"{e.Code} {e.Description}")));
|
||||
}
|
||||
|
||||
return userToCreate.Id;
|
||||
|
||||
Reference in New Issue
Block a user