Skip to content

Commit

Permalink
Adding User Manager (#21)
Browse files Browse the repository at this point in the history
* Adding PoC user manager
  • Loading branch information
A-Guldborg authored Jan 25, 2024
1 parent 9b587a4 commit e202646
Show file tree
Hide file tree
Showing 10 changed files with 847 additions and 47 deletions.
358 changes: 337 additions & 21 deletions Shifty.Api/Generated/AnalogCoreV2/AnalogCoreV2.cs

Large diffs are not rendered by default.

125 changes: 125 additions & 0 deletions Shifty.App/Components/UserTable.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
@namespace Components
@using System.ComponentModel.DataAnnotations
@using Shifty.App.Services
@using Shifty.Api.Generated.AnalogCoreV1
@using Shifty.Api.Generated.AnalogCoreV2
@using Shared
@using LanguageExt.UnsafeValueAccess
@inject IUserService _userService
@inject ISnackbar Snackbar

<MudContainer MaxWidth="MaxWidth.Medium" Style="margin-top: 20px;">
<MudTable
T="SimpleUserResponse"
ServerData="@(new Func<TableState, Task<TableData<SimpleUserResponse>>>(LoadUsers))"
@ref="_table"
EditTrigger="TableEditTrigger.EditButton"
ApplyButtonPosition="TableApplyButtonPosition.End"
EditButtonPosition="TableEditButtonPosition.End"
CanCancelEdit="true"
RowEditCancel="ResetUserOnCancel"
RowEditCommit="UpdateUserGroup"
RowEditPreview="BackupUser"
IsEditRowSwitchingBlocked="true">
<ToolBarContent>
<MudText Typo="Typo.h6">Users</MudText>
<MudSpacer />
<MudTextField
T="string"
ValueChanged="@(s => OnSearch(s))"
Placeholder="Search"
Adornment="Adornment.Start"
AdornmentIcon="Icons.Material.Filled.Search"
IconSize="Size.Medium" />
</ToolBarContent>
<HeaderContent>
<MudTh>Id</MudTh>
<MudTh>Name</MudTh>
<MudTh>Email</MudTh>
<MudTh>UserGroup</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Id">@context.Id</MudTd>
<MudTd DataLabel="Name">@context.Name</MudTd>
<MudTd DataLabel="Email">@context.Email</MudTd>
<MudTd DataLabel="UserGroup">@context.UserGroup</MudTd>
</RowTemplate>
<RowEditingTemplate>
<MudTd DataLabel="Id">@context.Id</MudTd>
<MudTd DataLabel="Name">@context.Name</MudTd>
<MudTd DataLabel="Email">@context.Email</MudTd>
<MudTd DataLabel="UserGroup">
<MudSelect T="UserGroup" Label="UserGroup" @bind-Value="context.UserGroup">
@foreach (var group in Enum.GetValues<UserGroup>()) {
<MudSelectItem T="UserGroup" Value="@group">@group</MudSelectItem>
}
</MudSelect>
</MudTd>
</RowEditingTemplate>
<NoRecordsContent>No matching records found</NoRecordsContent>
<PagerContent><MudTablePager /></PagerContent>
<EditButtonContent Context="button">
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Outlined.Edit" OnClick="@button.ButtonAction" Disabled="@button.ButtonDisabled" />
</EditButtonContent>
</MudTable>
</MudContainer>

@code
{
private MudTable<SimpleUserResponse> _table;
private string searchString = "";
UserGroup UserGroupBeforeEdit;

private async Task<TableData<SimpleUserResponse>> LoadUsers(TableState state)
{
var result = await _userService.SearchUsers(searchString, state.Page, state.PageSize);

return result.Match(
Succ: res => {
return new TableData<SimpleUserResponse>(){ Items = res.Users.AsEnumerable(), TotalItems = res.TotalUsers};;
},
Fail: error => {
Snackbar.Add(error.Message, Severity.Error);
return new TableData<SimpleUserResponse>(){ Items = new List<SimpleUserResponse>(), TotalItems = 0};
}
);
}

private void OnSearch(string search)
{
searchString = search;
_table.ReloadServerData();
}

private void UpdateUserGroup(object element)
{
var user = (SimpleUserResponse)element;

var result = _userService.UpdateUserGroupAsync(user.Id, user.UserGroup);

result.Match(
Succ: user => {
Snackbar.Add("User updated", Severity.Success);
},
Fail: error => {
Snackbar.Add(error.Message, Severity.Error);
}
);

StateHasChanged();
}


private void BackupUser(object element)
{
UserGroupBeforeEdit = ((SimpleUserResponse)element).UserGroup;
StateHasChanged();
}

private void ResetUserOnCancel(object user)
{
((SimpleUserResponse)user).UserGroup = UserGroupBeforeEdit;
StateHasChanged();
Snackbar.Add("Canceled editing", Severity.Warning);
}
}
24 changes: 24 additions & 0 deletions Shifty.App/Pages/UserManagement.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@page "/Users"
@using Components
@inject NavigationManager NavManager

@if (_user is not null && _user.IsInRole("Board"))
{
<UserTable />
}

@code {
[CascadingParameter] public Task<AuthenticationState> AuthTask { get; set; }
private System.Security.Claims.ClaimsPrincipal _user;

protected override async Task OnInitializedAsync()
{
var authState = await AuthTask;
_user = authState.User;

if (_user is null || !_user.IsInRole("Board"))
{
NavManager.NavigateTo("/");
}
}
}
1 change: 1 addition & 0 deletions Shifty.App/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public static void ConfigureServices(IServiceCollection services, IConfiguration
services.AddScoped<IAuthenticationService, AuthenticationService>();
services.AddScoped<IVoucherService, VoucherService>();
services.AddScoped<IProductService, ProductService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<RequestAuthenticationHandler>();

services.AddMudServices(config =>
Expand Down
27 changes: 21 additions & 6 deletions Shifty.App/Repositories/AccountRepository.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using LanguageExt;
using LanguageExt.ClassInstances.Pred;
using LanguageExt.Common;
using Shifty.Api.Generated.AnalogCoreV1;
using Shifty.Api.Generated.AnalogCoreV2;
using static LanguageExt.Prelude;

namespace Shifty.App.Repositories
{
public class AccountRepository : IAccountRepository
{
private readonly AnalogCoreV1 _client;
private readonly AnalogCoreV1 _v1client;
private readonly AnalogCoreV2 _v2client;

public AccountRepository(AnalogCoreV1 client)
public AccountRepository(AnalogCoreV1 v1client, AnalogCoreV2 v2client)
{
_client = client;
_v1client = v1client;
_v2client = v2client;
}
public async Task<Either<Error, TokenDto>> LoginAsync(string username, string password)

public async Task<Either<Error, TokenDto>> LoginAsync(string username, string password)
{
var dto = new LoginDto()
{
Email= username,
Password = password,
Version = "2.1.0"
};
return await TryAsync(_client.ApiV1AccountLoginAsync(loginDto: dto)).ToEither();
return await TryAsync(_v1client.ApiV1AccountLoginAsync(loginDto: dto)).ToEither();
}

public async Task<Try<UserSearchResponse>> SearchUserAsync(string query, int page, int pageSize)
{
return await TryAsync(_v2client.ApiV2AccountSearchAsync(filter: query, pageNum: page, pageLength: pageSize));
}

public async Task<Try<Task>> UpdateUserGroupAsync(int userId, UserGroup group)
{
return await TryAsync(_v2client.ApiV2AccountUserGroupAsync(userId, new(){UserGroup = group}));
}
}
}
4 changes: 4 additions & 0 deletions Shifty.App/Repositories/IAccountRepository.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using LanguageExt;
using LanguageExt.Common;
using Shifty.Api.Generated.AnalogCoreV1;
using Shifty.Api.Generated.AnalogCoreV2;

namespace Shifty.App.Repositories
{
public interface IAccountRepository
{
public Task<Either<Error, TokenDto>> LoginAsync(string username, string password);
public Task<Try<UserSearchResponse>> SearchUserAsync(string query, int page, int pageSize);
public Task<Try<Task>> UpdateUserGroupAsync(int userId, UserGroup group);
}
}
14 changes: 14 additions & 0 deletions Shifty.App/Services/IUserService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using LanguageExt;
using LanguageExt.Common;
using Shifty.Api.Generated.AnalogCoreV2;

namespace Shifty.App.Services
{
public interface IUserService
{
Task<Try<UserSearchResponse>> SearchUsers(string query, int pageNumber = 0, int pageSize = 10);
Task<Try<Task>> UpdateUserGroupAsync(int userId, UserGroup group);
}
}
30 changes: 30 additions & 0 deletions Shifty.App/Services/UserService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Blazored.LocalStorage;
using LanguageExt;
using LanguageExt.Common;
using Shifty.Api.Generated.AnalogCoreV2;
using Shifty.App.Repositories;

namespace Shifty.App.Services
{
public class UserService : IUserService
{
private readonly IAccountRepository _accountRepository;

public UserService(IAccountRepository accountRepository)
{
_accountRepository = accountRepository;
}

public Task<Try<UserSearchResponse>> SearchUsers(string query, int pageNumber = 0, int pageSize = 10)
{
return _accountRepository.SearchUserAsync(query, pageNumber, pageSize);
}

public Task<Try<Task>> UpdateUserGroupAsync(int userId, UserGroup group)
{
return _accountRepository.UpdateUserGroupAsync(userId, group);
}
}
}
1 change: 1 addition & 0 deletions Shifty.App/Shared/NavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
{
<MudNavLink Href="Voucher" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Sell">Issue vouchers</MudNavLink>
<MudNavLink Href="Products" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Outlined.Inventory">Product Management</MudNavLink>
<MudNavLink Href="Users" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.ManageAccounts">Manage users</MudNavLink>
}
</MudNavMenu>

Expand Down
Loading

0 comments on commit e202646

Please sign in to comment.