Add user manager page (#22)
Reviewed-on: #22
This commit was merged in pull request #22.
This commit is contained in:
@@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Games;
|
||||
|
||||
public partial class Game
|
||||
public partial class Games
|
||||
{
|
||||
[Inject] private IGameGateway GameGateway { get; set; } = default!;
|
||||
|
||||
@@ -59,7 +59,7 @@ public class GameGateway(IHttpClientService httpClientService) : IGameGateway
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new CategoryNotFoundException(ResourcesKey.ErrorFetchGames);
|
||||
throw new GameNotFoundException(ResourcesKey.ErrorFetchGames);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@using GameIdeas.BlazorApp.Pages.Games
|
||||
@using GameIdeas.BlazorApp.Pages.User
|
||||
@using GameIdeas.BlazorApp.Pages.UserMenu
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
|
||||
@using GameIdeas.BlazorApp.Shared.Models
|
||||
@@ -10,32 +10,35 @@
|
||||
@inherits ComponentBase
|
||||
|
||||
<div class="header-tab">
|
||||
<div class="icon-container" @onclick="HandleIconClicked">
|
||||
<a href="/Games" class="icon-container">
|
||||
<img src="icon.png" alt="Game Ideas">
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@ChildContent
|
||||
|
||||
<div class="account-add-container">
|
||||
<AuthorizeView Roles="@GlobalConstants.ADMIN_MEMBER">
|
||||
<Authorized>
|
||||
<div class="add-buttons">
|
||||
<div class="first-button button">
|
||||
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<Select @ref="SelectListAdd" TItem="KeyValuePair<AddType, string>" THeader="object"
|
||||
ValuesChanged=HandleAddTypeClicked Params=SelectParams Theme="SelectTheme.Navigation">
|
||||
<div class="second-button button">
|
||||
@if (DisplayAdd)
|
||||
{
|
||||
<AuthorizeView Roles="@GlobalConstants.ADMIN_MEMBER">
|
||||
<Authorized>
|
||||
<div class="add-buttons">
|
||||
<div class="first-button button">
|
||||
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M1 3H23L12 22" />
|
||||
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</Select>
|
||||
</div>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
<Select @ref="SelectListAdd" TItem="KeyValuePair<AddType, string>" THeader="object"
|
||||
ValuesChanged=HandleAddTypeClicked Params=SelectParams Theme="SelectTheme.Navigation">
|
||||
<div class="second-button button">
|
||||
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M1 3H23L12 22" />
|
||||
</svg>
|
||||
</div>
|
||||
</Select>
|
||||
</div>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
}
|
||||
|
||||
<UserMenu />
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace GameIdeas.BlazorApp.Pages.Games.Header;
|
||||
|
||||
public partial class GameHeader : ComponentBase
|
||||
{
|
||||
[Parameter] public bool DisplayAdd { get; set; } = true;
|
||||
[Parameter] public EventCallback<AddType> AddTypeChanged { get; set; }
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
@@ -31,11 +32,6 @@ public partial class GameHeader : ComponentBase
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private void HandleIconClicked()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private async Task HandleAddTypeClicked(IEnumerable<KeyValuePair<AddType, string>> values)
|
||||
{
|
||||
SelectListAdd?.Close();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@using Blazored.FluentValidation
|
||||
|
||||
<EditForm EditContext="EditContext" OnSubmit="HandleLoginSubmit">
|
||||
<FluentValidationValidator />
|
||||
<FluentValidationValidator Validator="Validator" />
|
||||
<div class="login-form">
|
||||
<div class="login-field">
|
||||
<div class="input-title">@ResourcesKey.EnterUsername</div>
|
||||
@@ -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">
|
||||
@@ -1,9 +1,9 @@
|
||||
using GameIdeas.BlazorApp.Pages.User.Gateways;
|
||||
using GameIdeas.BlazorApp.Pages.UserMenu.Gateways;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.User.Components;
|
||||
namespace GameIdeas.BlazorApp.Pages.UserMenu.Components;
|
||||
|
||||
public partial class Login
|
||||
{
|
||||
@@ -12,7 +12,7 @@ public partial class Login
|
||||
private EditContext? EditContext;
|
||||
private UserDto UserDto = new();
|
||||
private bool IsLoading = false;
|
||||
|
||||
private LoginValidator Validator = new();
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
EditContext = new EditContext(UserDto);
|
||||
@@ -1,7 +1,7 @@
|
||||
using FluentValidation;
|
||||
using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.User.Components;
|
||||
namespace GameIdeas.BlazorApp.Pages.UserMenu.Components;
|
||||
|
||||
public class LoginValidator : AbstractValidator<UserDto>
|
||||
{
|
||||
@@ -5,7 +5,7 @@ using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.User.Gateways;
|
||||
namespace GameIdeas.BlazorApp.Pages.UserMenu.Gateways;
|
||||
|
||||
public class AuthGateway(IHttpClientService httpClient,
|
||||
AuthenticationStateProvider stateProvider) : IAuthGateway
|
||||
@@ -1,6 +1,6 @@
|
||||
using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.User.Gateways;
|
||||
namespace GameIdeas.BlazorApp.Pages.UserMenu.Gateways;
|
||||
|
||||
public interface IAuthGateway
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
@using GameIdeas.BlazorApp.Pages.User.Components
|
||||
@using GameIdeas.BlazorApp.Pages.UserMenu.Components
|
||||
@using GameIdeas.BlazorApp.Shared.Components.BackdropFilter
|
||||
@using GameIdeas.BlazorApp.Shared.Constants
|
||||
@using GameIdeas.Shared.Constants
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
<AuthorizeView Roles="@GlobalConstants.ADMINISTRATOR">
|
||||
<Authorized>
|
||||
<div class="menu-element">
|
||||
<a class="menu-element" href="/Users">
|
||||
@ResourcesKey.UserManager
|
||||
</div>
|
||||
</a>
|
||||
<span class="line"></span>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
@@ -1,20 +1,20 @@
|
||||
using GameIdeas.BlazorApp.Pages.User.Gateways;
|
||||
using GameIdeas.BlazorApp.Services;
|
||||
using GameIdeas.BlazorApp.Pages.UserMenu.Gateways;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.User;
|
||||
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()
|
||||
@@ -36,6 +36,8 @@
|
||||
}
|
||||
|
||||
.menu-element {
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
padding: 0 20px;
|
||||
align-content: center;
|
||||
@@ -0,0 +1,34 @@
|
||||
@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">
|
||||
<InputText class="input-name" @bind-Value="@User.Username" disabled="@(!IsEditing)" />
|
||||
</div>
|
||||
<div class="password">
|
||||
<InputText type="password" class="input-password" placeholder="********" @bind-Value="@User.Password" disabled="@(!IsEditing)" />
|
||||
</div>
|
||||
<div class="role">
|
||||
<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>
|
||||
@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>
|
||||
@@ -0,0 +1,81 @@
|
||||
using FluentValidation;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users.Components;
|
||||
|
||||
public partial class UserRow
|
||||
{
|
||||
[Parameter] public UserDto User { get; set; } = new();
|
||||
[Parameter] public List<RoleDto> Roles { get; set; } = [];
|
||||
[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;
|
||||
private UserDto? OriginalUser;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
EditContext = new(User);
|
||||
OriginalUser = new()
|
||||
{
|
||||
Username = User.Username,
|
||||
Role = User.Role
|
||||
};
|
||||
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
SelectRoleParams = new()
|
||||
{
|
||||
GetItemLabel = role => role.Name,
|
||||
Items = Roles
|
||||
};
|
||||
|
||||
base.OnParametersSet();
|
||||
}
|
||||
|
||||
private void HandleValuesChanged(IEnumerable<RoleDto> roles)
|
||||
{
|
||||
User.Role = roles.FirstOrDefault();
|
||||
}
|
||||
|
||||
private async Task HandleSubmitClicked()
|
||||
{
|
||||
if (EditContext?.Validate() == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsEditing = false;
|
||||
await OnSubmit.InvokeAsync(User);
|
||||
User.Password = null;
|
||||
}
|
||||
|
||||
|
||||
private void HandleEditClicked()
|
||||
{
|
||||
if (IsEditing)
|
||||
{
|
||||
User.Username = OriginalUser?.Username;
|
||||
User.Role = OriginalUser?.Role;
|
||||
User.Password = null;
|
||||
}
|
||||
|
||||
IsEditing = !IsEditing;
|
||||
}
|
||||
|
||||
private async Task HandleRemoveClicked()
|
||||
{
|
||||
await OnRemove.InvokeAsync(User);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
.row {
|
||||
height: 64px;
|
||||
display: grid;
|
||||
grid-template-columns: 48px 1fr 1fr 1fr auto;
|
||||
grid-gap: 8px;
|
||||
padding: 0 8px;
|
||||
background: var(--input-secondary);
|
||||
box-shadow: var(--drop-shadow);
|
||||
border-radius: var(--big-radius);
|
||||
}
|
||||
|
||||
.row > * {
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.icon ::deep svg {
|
||||
fill: var(--line);
|
||||
}
|
||||
|
||||
.role {
|
||||
min-width: 160px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
::deep .input-name,
|
||||
::deep .input-name[disabled],
|
||||
::deep .input-password,
|
||||
::deep .input-password[disabled],
|
||||
::deep .input-password::placeholder {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
::deep .input-name,
|
||||
::deep .input-password,
|
||||
.role-label {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--small-radius);
|
||||
border: solid 1px var(--line);
|
||||
background: rgb(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
::deep .input-name[disabled],
|
||||
::deep .input-password[disabled],
|
||||
.disabled {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.role-label {
|
||||
display: block;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
height:auto;
|
||||
}
|
||||
|
||||
.buttons > * {
|
||||
border: none;
|
||||
outline: none;
|
||||
margin: auto;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
background: none;
|
||||
border-radius: var(--small-radius);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.buttons > *:hover, .selected {
|
||||
background: var(--input-selected)
|
||||
}
|
||||
|
||||
.remove ::deep svg {
|
||||
fill: var(--red);
|
||||
}
|
||||
|
||||
.edit ::deep svg {
|
||||
fill: var(--yellow);
|
||||
}
|
||||
|
||||
.submit ::deep svg {
|
||||
fill: var(--green);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
@using GameIdeas.BlazorApp.Shared.Constants
|
||||
|
||||
<div class="row">
|
||||
<div class="icon">@Icons.Account</div>
|
||||
<div class="name pill"></div>
|
||||
<div class="password pill"></div>
|
||||
<div class="role pill"></div>
|
||||
<div class="buttons">
|
||||
<span class="remove">@Icons.Bin</span>
|
||||
<span class="edit">@Icons.Pen</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,69 @@
|
||||
.row {
|
||||
height: 64px;
|
||||
display: grid;
|
||||
grid-template-columns: 48px 1fr 1fr 1fr auto;
|
||||
grid-gap: 8px;
|
||||
padding: 0 8px;
|
||||
background: var(--input-secondary);
|
||||
box-shadow: var(--drop-shadow);
|
||||
border-radius: var(--big-radius);
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.icon ::deep svg {
|
||||
fill: var(--line);
|
||||
}
|
||||
|
||||
.pill {
|
||||
animation: loading 3s ease infinite;
|
||||
align-self: center;
|
||||
height: 28px;
|
||||
border-radius: var(--small-radius);
|
||||
box-sizing: border-box;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.buttons > * {
|
||||
margin: auto;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
background: none;
|
||||
border-radius: var(--small-radius);
|
||||
padding: 2px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.remove ::deep svg {
|
||||
fill: var(--red);
|
||||
}
|
||||
|
||||
.edit ::deep svg {
|
||||
fill: var(--yellow);
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
background: rgb(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
50% {
|
||||
background: rgb(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
100% {
|
||||
background: rgb(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using FluentValidation;
|
||||
using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users.Components;
|
||||
|
||||
public class UserCreateValidator : AbstractValidator<UserDto>
|
||||
{
|
||||
public UserCreateValidator()
|
||||
{
|
||||
RuleFor(user => user.Username)
|
||||
.NotEmpty()
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
|
||||
RuleFor(user => user.Password)
|
||||
.NotEmpty()
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
|
||||
RuleFor(user => user.Role)
|
||||
.NotEmpty()
|
||||
.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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users.Filters;
|
||||
|
||||
public class UserFilterParams
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public List<RoleDto>? Roles { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using GameIdeas.BlazorApp.Pages.Users.Filters;
|
||||
using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users.Gateways;
|
||||
|
||||
public interface IUserGateway
|
||||
{
|
||||
Task<UserListDto> GetUsers(UserFilterParams filterParams, int currentPage);
|
||||
Task<IEnumerable<RoleDto>> GetRoles();
|
||||
Task<IdDto> CreateUser(UserDto user);
|
||||
Task<IdDto> UpdateUser(UserDto user);
|
||||
Task<IdDto> DeleteUser(string userId);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using GameIdeas.BlazorApp.Pages.Users.Filters;
|
||||
using GameIdeas.BlazorApp.Services;
|
||||
using GameIdeas.BlazorApp.Shared.Constants;
|
||||
using GameIdeas.BlazorApp.Shared.Exceptions;
|
||||
using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users.Gateways;
|
||||
|
||||
public class UserGateway(IHttpClientService httpClient) : IUserGateway
|
||||
{
|
||||
public async Task<IdDto> CreateUser(UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.PostAsync<IdDto>(Endpoints.User.Create, user)
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorCreateUser);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new UserCreationException(ResourcesKey.ErrorCreateUser);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IdDto> DeleteUser(string userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.DeleteAsync<IdDto>(Endpoints.User.Delete(userId))
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorDeleteUser);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new UserCreationException(ResourcesKey.ErrorDeleteUser);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RoleDto>> GetRoles()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.FetchDataAsync<IEnumerable<RoleDto>>(Endpoints.User.Roles)
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorFetchRoles);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new RoleNotFoundException(ResourcesKey.ErrorFetchRoles);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<UserListDto> GetUsers(UserFilterParams filterParams, int currentPage)
|
||||
{
|
||||
try
|
||||
{
|
||||
UserFilterDto filter = new()
|
||||
{
|
||||
CurrentPage = currentPage,
|
||||
Name = filterParams.Name,
|
||||
RoleIds = filterParams.Roles?.Select(r => r.Id)
|
||||
};
|
||||
|
||||
var url = Endpoints.User.Fetch(filter);
|
||||
return await httpClient.FetchDataAsync<UserListDto>(url)
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorFetchUsers);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new UserNotFoundException(ResourcesKey.ErrorFetchUsers);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IdDto> UpdateUser(UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.PutAsync<IdDto>(Endpoints.User.Update(user.Id ?? string.Empty), user)
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorUpdateUser);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new UserCreationException(ResourcesKey.ErrorUpdateUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@page "/Users"
|
||||
@using GameIdeas.BlazorApp.Pages.Games.Header
|
||||
@using GameIdeas.BlazorApp.Layouts
|
||||
@using GameIdeas.BlazorApp.Pages.Users.Components
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Popup
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Popup.Components
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Search
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
|
||||
@using GameIdeas.BlazorApp.Shared.Components.SelectSearch
|
||||
@using GameIdeas.Shared.Dto
|
||||
|
||||
@layout MainLayout
|
||||
|
||||
<PageTitle>@ResourcesKey.GamesIdeas</PageTitle>
|
||||
|
||||
<GameHeader DisplayAdd="false">
|
||||
<div class="header-content">
|
||||
<SearchInput Placeholder="@ResourcesKey.EnterUsername" @bind-Text="FilterParams.Name" @bind-Text:after=HandleFilterChanged />
|
||||
<SelectSearch TItem="RoleDto" Placeholder="@ResourcesKey.Roles" @bind-Values="FilterParams.Roles" @bind-Values:after=HandleFilterChanged
|
||||
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="HandleOpenConfirmationPopup" OnSubmit="HandleUpdateUser" Validator="@(new UserUpdateValidator())" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@for (int i = 0; i < 20; i++)
|
||||
{
|
||||
<UserRowSkeleton />
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Popup @ref=Popup Closable=false>
|
||||
<ConfirmDelete OnCancel="HandleCancelPopupClicked" OnConfirm="HandleRemoveUser" />
|
||||
</Popup>
|
||||
@@ -0,0 +1,133 @@
|
||||
using GameIdeas.BlazorApp.Pages.Users.Filters;
|
||||
using GameIdeas.BlazorApp.Pages.Users.Gateways;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Popup;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users;
|
||||
|
||||
public partial class Users
|
||||
{
|
||||
[Inject] private IUserGateway UserGateway { get; set; } = default!;
|
||||
|
||||
private Popup? Popup;
|
||||
private bool IsLoading = false;
|
||||
private UserFilterParams FilterParams = new();
|
||||
private UserListDto UserList = new();
|
||||
private IEnumerable<RoleDto> Roles = [];
|
||||
private int CurrentPage = 1;
|
||||
private UserDto UserAdd = new();
|
||||
private UserDto? UserDelete;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await FetchData();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private async Task FetchData(bool fetchRoles = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
if (fetchRoles)
|
||||
Roles = await UserGateway.GetRoles();
|
||||
|
||||
UserList = await UserGateway.GetUsers(FilterParams, CurrentPage);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleSubmitUser(UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
await UserGateway.CreateUser(user);
|
||||
await FetchData(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
|
||||
UserAdd = new();
|
||||
}
|
||||
|
||||
private async Task HandleUpdateUser(UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
await UserGateway.UpdateUser(user);
|
||||
await FetchData(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleRemoveUser()
|
||||
{
|
||||
Popup?.Close();
|
||||
|
||||
if (UserDelete?.Id == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
await UserGateway.DeleteUser(UserDelete.Id);
|
||||
await FetchData(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
|
||||
UserDelete = null;
|
||||
}
|
||||
private void HandleResetUser(UserDto args)
|
||||
{
|
||||
UserAdd = new();
|
||||
}
|
||||
private async Task HandleFilterChanged()
|
||||
{
|
||||
await FetchData(false);
|
||||
}
|
||||
private void HandleCancelPopupClicked()
|
||||
{
|
||||
Popup?.Close();
|
||||
}
|
||||
private void HandleOpenConfirmationPopup(UserDto user)
|
||||
{
|
||||
UserDelete = user;
|
||||
Popup?.Open();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
.header-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
::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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.container {
|
||||
padding: 20px 20px;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user