Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion UserRepositoryTest.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35303.130
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UserRepositoryTest", "UserRepositoryTest\UserRepositoryTest.csproj", "{23167AAE-E19D-48D3-9835-544A6C01A23E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserRepositoryTest", "UserRepositoryTest\UserRepositoryTest.csproj", "{23167AAE-E19D-48D3-9835-544A6C01A23E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
57 changes: 57 additions & 0 deletions UserRepositoryTest/Components/InputModal.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<div class="modal @(_showModal?"show":"")" style="display:@(_showModal?"block":"hidden")" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@_title</h5>
</div>
<div class="modal-body">
<div class="input-group">
<span class="input-group-text">@_propertyName</span>
<input type="text" aria-label="Title" class="form-control" @bind="_input" maxlength="64"
oninput="this.value = this.value.replace(/[^A-Za-z0-9ŽžÀ-ÿ _]/g, '');">
</div>
</div>
<div class="modal-footer d-flex flex-row justify-content-evenly">
<button type="button" class="flex-grow-1 btn btn-light" @onclick="Close">Cancel</button>
<button type="button" class="flex-grow-1 btn btn-primary" @onclick="SaveChanges">Ok</button>
</div>
</div>
</div>
</div>

@if (_showModal)
{
<div class="modal-backdrop fade show"></div>
}

@code {
private bool _showModal { get; set; } = false;
private TaskCompletionSource<string?> taskCompletionSource = null!;
private string _input = "";
private string _title = "";
private string _propertyName = "";

public Task<string?> Show(string title, string propertyName)
{
_title = title;
_propertyName = propertyName;
_input = "";
taskCompletionSource = new TaskCompletionSource<string?>();
_showModal = true;
StateHasChanged();

return taskCompletionSource.Task;
}

private void Close()
{
_showModal = false;
taskCompletionSource.SetResult(null);
}

private void SaveChanges()
{
_showModal = false;
taskCompletionSource.SetResult(_input);
}
}
59 changes: 59 additions & 0 deletions UserRepositoryTest/Components/Pagination.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<ul class="pagination pagination-sm">
<!-- Previous Button -->
<li class="page-item @(CurrentPage == 1 ? "disabled" : "")">
<a class="page-link" href="#" @onclick="PreviousPage" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>

<!-- Page Numbers -->
@foreach (var pageIndex in PaginationHelper.GetPageNumbers(CurrentPage, MaxPage))
{
<li class="page-item @(pageIndex == CurrentPage ? "active" : "")">
<a class="page-link" href="#" @onclick="() => GoToPage(pageIndex)">@pageIndex</a>
</li>
}

<!-- Next Button -->
<li class="page-item @(CurrentPage == MaxPage ? "disabled" : "")">
<a class="page-link" href="#" @onclick="NextPage" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>

@code {
[Parameter]
public int CurrentPage { get; set; } = 1;

[Parameter]
public int MaxPage { get; set; } = 1;

[Parameter]
public EventCallback<int> OnPageChanged { get; set; }

private void GoToPage(int page)
{
if (page < 1 || page > MaxPage || page == CurrentPage)
return;

OnPageChanged.InvokeAsync(page);
}

private void PreviousPage()
{
if (CurrentPage > 1)
{
GoToPage(CurrentPage - 1);
}
}

private void NextPage()
{
if (CurrentPage < MaxPage)
{
GoToPage(CurrentPage + 1);
}
}

}
40 changes: 40 additions & 0 deletions UserRepositoryTest/Components/ToastMessage/ToastContainer.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<div class="toast-container position-fixed start-0 top-0 w-100 p-4 v-stack">
@foreach (var toast in _toasts)
{
<div class="toast me-auto ms-auto bg-@toast.Color text-bg-@toast.Color border-0 show" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
@toast.Message
</div>
<button type="button" class="btn-close me-2 m-auto" aria-label="Close"
@onclick="(args) => RemoveWithoutNotify(toast)"></button>
</div>
</div>
}
</div>

@code {
private List<Toast> _toasts = new List<Toast>();

public Toast CreateToast(string message, string color)
{
var toast = new Toast(message, color);
_toasts.Add(toast);
StateHasChanged();

return toast;
}

public void RemoveToast(Toast toast)
{
RemoveWithoutNotify(toast);
StateHasChanged();
}

private void RemoveWithoutNotify(Toast toast)
{
_toasts.Remove(toast);
}

public record Toast(string Message, string Color);
}
56 changes: 56 additions & 0 deletions UserRepositoryTest/Components/ToastMessage/ToastHandler.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@implements IToastMessageHandler

<CascadingValue Value="@((IToastMessageHandler)this)" IsFixed="true">
@ChildContent
</CascadingValue>

<ToastContainer @ref="_toastContainer" />

@code {
private const int HIDE_DELAY_IN_SEC = 5;
private ToastContainer? _toastContainer;

[Parameter]
public TimeSpan DefaultAutoHideDelay { get; set; } = TimeSpan.FromSeconds(HIDE_DELAY_IN_SEC);

[Parameter]
public RenderFragment? ChildContent { get; set; }

public void Info(string message, bool autoHide, TimeSpan? autoHideDelay = null)
{
var toast = _toastContainer?.CreateToast(message, "info");
_ = HideToast(toast, autoHide, autoHideDelay);
}

public void Success(string message, bool autoHide, TimeSpan? autoHideDelay = null)
{
var toast = _toastContainer?.CreateToast(message, "success");
_ = HideToast(toast, autoHide, autoHideDelay);

}

public void Danger(string message, bool autoHide, TimeSpan? autoHideDelay = null)
{
var toast = _toastContainer?.CreateToast(message, "danger");
_ = HideToast(toast, autoHide, autoHideDelay);

}

public void Warning(string message, bool autoHide, TimeSpan? autoHideDelay = null)
{
var toast = _toastContainer?.CreateToast(message, "warning");
_ = HideToast(toast, autoHide, autoHideDelay);

}

private async Task HideToast(ToastContainer.Toast? toast, bool autoHide, TimeSpan? autoHideDelay = null)
{
if (autoHide && toast != null)
{
await Task.Delay(autoHideDelay ?? DefaultAutoHideDelay);
_toastContainer?.RemoveToast(toast);
}
}
}


70 changes: 70 additions & 0 deletions UserRepositoryTest/Components/UserAccounts.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
@inject IUserAccountService UserAccountService

<h6 class="border-bottom p-2 mb-0 d-flex align-items-center">
<span class="ellipsis">@_selectedUser?.Name</span>
<span class="flex-grow-1 text-nowrap">&nbsp;> Accounts</span>
<button type="button" class="btn btn-light btn-sm ms-auto text-nowrap" @onclick="CreateAccount">Create new</button>
</h6>
@foreach (var account in _accounts)
{
var color = account.IsActive ? "#5e919f" : "gray";
<div class="d-flex text-body-secondary p-2">
<svg class="bd-placeholder-img flex-shrink-0 me-2 rounded" width="32" height="32" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder" preserveAspectRatio="xMidYMid slice" focusable="false">
<title>Placeholder</title>
<rect width="100%" height="100%" fill="@color"></rect>
</svg>
<div class="pb-3 mb-0 small lh-sm border-bottom w-100">
<div class="d-flex justify-content-between">
<strong class="text-gray-dark ellipsis">@account.Name</strong>
<div class="form-switch">
<input type="checkbox" class="form-check-input" role="switch" checked="@account.IsActive" @oninput="(args) => SetActive(account, args)" />
</div>
</div>
<span class="d-block id-span">#@account.Id</span>
</div>
</div>
}

@code {
private IEnumerable<AccountDto> _accounts = [];
private UserDto? _selectedUser = null!;

[CascadingParameter]
public IInputModal InputModal { get; set; } = null!;

[CascadingParameter]
public IToastMessageHandler Toast { get; set; } = null!;

[Parameter, EditorRequired]
public required Guid SelectedUserId { get; set; }

protected override void OnParametersSet()
{
_selectedUser = UserAccountService.GetUser(SelectedUserId);
_accounts = UserAccountService.GetAccounts(SelectedUserId);
}

private async Task CreateAccount()
{
var accountName = await InputModal.Show("Create New Account", "Account Name");
if (accountName is null) return;

if (!string.IsNullOrEmpty(accountName))
{
if (UserAccountService.AddAccount(SelectedUserId, accountName))
{
_accounts = UserAccountService.GetAccounts(SelectedUserId);
Toast.Success("Account has been successfully created!", autoHide: true);
return;
}
}

Toast.Danger("Account creation failed.", autoHide: true);
}

private void SetActive(AccountDto account, ChangeEventArgs args)
{
var active = (bool?)args.Value;
UserAccountService.SetAccountActive(SelectedUserId, account.Id, active ?? account.IsActive);
}
}
31 changes: 19 additions & 12 deletions UserRepositoryTest/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
@implements IInputModal

<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<NavMenu />

<article class="content px-4">
<main class="container">
<ToastHandler>
<CascadingValue Value="@((IInputModal)this)">
@Body
</article>
</main>
</div>
</CascadingValue>
</ToastHandler>
</main>

<InputModal @ref="inputModal" />

@code {
private InputModal? inputModal;

public Task<string?> Show(string title, string propertyName)
{
return inputModal!.Show(title, propertyName);
}
}
Loading