diff --git a/UserRepositoryTest.sln b/UserRepositoryTest.sln index b265efe..7d964d5 100644 --- a/UserRepositoryTest.sln +++ b/UserRepositoryTest.sln @@ -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 diff --git a/UserRepositoryTest/Components/InputModal.razor b/UserRepositoryTest/Components/InputModal.razor new file mode 100644 index 0000000..c39f5c2 --- /dev/null +++ b/UserRepositoryTest/Components/InputModal.razor @@ -0,0 +1,57 @@ + + +@if (_showModal) +{ + +} + +@code { + private bool _showModal { get; set; } = false; + private TaskCompletionSource taskCompletionSource = null!; + private string _input = ""; + private string _title = ""; + private string _propertyName = ""; + + public Task Show(string title, string propertyName) + { + _title = title; + _propertyName = propertyName; + _input = ""; + taskCompletionSource = new TaskCompletionSource(); + _showModal = true; + StateHasChanged(); + + return taskCompletionSource.Task; + } + + private void Close() + { + _showModal = false; + taskCompletionSource.SetResult(null); + } + + private void SaveChanges() + { + _showModal = false; + taskCompletionSource.SetResult(_input); + } +} \ No newline at end of file diff --git a/UserRepositoryTest/Components/Pagination.razor b/UserRepositoryTest/Components/Pagination.razor new file mode 100644 index 0000000..82189c7 --- /dev/null +++ b/UserRepositoryTest/Components/Pagination.razor @@ -0,0 +1,59 @@ +
    + +
  • + + + +
  • + + + @foreach (var pageIndex in PaginationHelper.GetPageNumbers(CurrentPage, MaxPage)) + { +
  • + @pageIndex +
  • + } + + +
  • + + + +
  • +
+ +@code { + [Parameter] + public int CurrentPage { get; set; } = 1; + + [Parameter] + public int MaxPage { get; set; } = 1; + + [Parameter] + public EventCallback 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); + } + } + +} diff --git a/UserRepositoryTest/Components/ToastMessage/ToastContainer.razor b/UserRepositoryTest/Components/ToastMessage/ToastContainer.razor new file mode 100644 index 0000000..851aec6 --- /dev/null +++ b/UserRepositoryTest/Components/ToastMessage/ToastContainer.razor @@ -0,0 +1,40 @@ +
+ @foreach (var toast in _toasts) + { + + } +
+ +@code { + private List _toasts = new List(); + + 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); +} diff --git a/UserRepositoryTest/Components/ToastMessage/ToastHandler.razor b/UserRepositoryTest/Components/ToastMessage/ToastHandler.razor new file mode 100644 index 0000000..005a7d3 --- /dev/null +++ b/UserRepositoryTest/Components/ToastMessage/ToastHandler.razor @@ -0,0 +1,56 @@ +@implements IToastMessageHandler + + + @ChildContent + + + + +@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); + } + } +} + + diff --git a/UserRepositoryTest/Components/UserAccounts.razor b/UserRepositoryTest/Components/UserAccounts.razor new file mode 100644 index 0000000..7d797b9 --- /dev/null +++ b/UserRepositoryTest/Components/UserAccounts.razor @@ -0,0 +1,70 @@ +@inject IUserAccountService UserAccountService + +
+ @_selectedUser?.Name +  > Accounts + +
+@foreach (var account in _accounts) +{ + var color = account.IsActive ? "#5e919f" : "gray"; +
+ + Placeholder + + +
+
+ @account.Name +
+ +
+
+ #@account.Id +
+
+} + +@code { + private IEnumerable _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); + } +} diff --git a/UserRepositoryTest/Layout/MainLayout.razor b/UserRepositoryTest/Layout/MainLayout.razor index 76eb725..1ad6cb3 100644 --- a/UserRepositoryTest/Layout/MainLayout.razor +++ b/UserRepositoryTest/Layout/MainLayout.razor @@ -1,16 +1,23 @@ @inherits LayoutComponentBase -
- +@implements IInputModal -
-
- About -
+ -
+
+ + @Body -
-
-
+ + + + + + +@code { + private InputModal? inputModal; + + public Task Show(string title, string propertyName) + { + return inputModal!.Show(title, propertyName); + } +} diff --git a/UserRepositoryTest/Layout/MainLayout.razor.css b/UserRepositoryTest/Layout/MainLayout.razor.css deleted file mode 100644 index ecf25e5..0000000 --- a/UserRepositoryTest/Layout/MainLayout.razor.css +++ /dev/null @@ -1,77 +0,0 @@ -.page { - position: relative; - display: flex; - flex-direction: column; -} - -main { - flex: 1; -} - -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; - display: flex; - align-items: center; -} - - .top-row ::deep a, .top-row ::deep .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - text-decoration: none; - } - - .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { - text-decoration: underline; - } - - .top-row ::deep a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - -@media (max-width: 640.98px) { - .top-row { - justify-content: space-between; - } - - .top-row ::deep a, .top-row ::deep .btn-link { - margin-left: 0; - } -} - -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - } - - .top-row { - position: sticky; - top: 0; - z-index: 1; - } - - .top-row.auth ::deep a:first-child { - flex: 1; - text-align: right; - width: 0; - } - - .top-row, article { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } -} diff --git a/UserRepositoryTest/Layout/NavMenu.razor b/UserRepositoryTest/Layout/NavMenu.razor index 48e779d..0d90027 100644 --- a/UserRepositoryTest/Layout/NavMenu.razor +++ b/UserRepositoryTest/Layout/NavMenu.razor @@ -1,29 +1,8 @@ -