diff --git a/Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor b/Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor index 24b1bbe..8befcab 100644 --- a/Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor +++ b/Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor @@ -3,9 +3,9 @@ @inject NavigationManager navigator
-

Login

-
+
+

Login

You're logged in as @context.User.Identity?.Name.
@@ -13,26 +13,32 @@ @if (errorList.Length > 0) { -
    - @foreach (var error in errorList) - { -
  • @error
  • - } -
+
+
    + @foreach (var error in errorList) + { +
  • @error
  • + } +
+
+
} - + - - + @if (isEmailSubmitted) + { + + - Reset password + Reset password + } - +
-
+
@@ -41,21 +47,49 @@ private string email = string.Empty; private string password = string.Empty; private string[] errorList = []; + private System.Threading.CancellationTokenSource? _clearErrorCts; + private bool isEmailSubmitted = false; + private Guid errorKey; - public async Task DoLoginAsync() + private async Task HandleSubmit() { - errorList = []; + if (!isEmailSubmitted) + { + SubmitEmail(); + } + else + { + await DoLoginAsync(); + } + } + + private void SubmitEmail() + { + SetErrors([]); if (string.IsNullOrWhiteSpace(email)) { - errorList = ["Email is required."]; + SetErrors(["Email is required."]); + return; + } + + isEmailSubmitted = true; + } + + public async Task DoLoginAsync() + { + SetErrors([]); + + if (string.IsNullOrWhiteSpace(email)) + { + SetErrors(["Email is required."]); return; } if (string.IsNullOrWhiteSpace(password)) { - errorList = ["Password is required."]; + SetErrors(["Password is required."]); return; } @@ -70,7 +104,34 @@ } else { - errorList = result.ErrorList; + SetErrors(result.ErrorList); + } + } + + private void SetErrors(string[] errors) + { + _clearErrorCts?.Cancel(); + errorList = errors; + + if (errors.Length > 0) + { + errorKey = Guid.NewGuid(); + _clearErrorCts = new System.Threading.CancellationTokenSource(); + _ = ClearErrorsAfterDelay(_clearErrorCts.Token); + } + } + + private async Task ClearErrorsAfterDelay(System.Threading.CancellationToken token) + { + try + { + await Task.Delay(10000, token); + errorList = []; + await InvokeAsync(StateHasChanged); + } + catch (TaskCanceledException) + { + // Ignore } } } \ No newline at end of file diff --git a/Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor.css b/Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor.css index 42ff5c7..f86dbcd 100644 --- a/Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor.css +++ b/Shogi/FrontEnd/Components/Pages/Identity/LoginPage.razor.css @@ -1,20 +1,52 @@ main { padding: 1rem; + display: grid; + place-content: flex-start center; } .LoginForm { grid-area: form; display: inline-grid; grid-template-areas: + "h1 h1" "errors errors" "emailLabel emailControl" "passLabel passControl" ". resetLink" "button button"; gap: 0.5rem 3rem; + color: var(--backgroundColor); + background-color: var(--middlegroundColor); + padding: 2rem; +} +.LoginForm a { + color: var(--middlegroundHrefColor); } .LoginForm .Errors { color: darkred; background-color: var(--foregroundColor); + display: flex; + flex-direction: column; } + + .LoginForm .Errors ul { + margin: 0.5rem 0; + } + +.ErrorProgress { + height: 5px; + background-color: darkred; + width: 100%; + animation: deplete 10s linear forwards; +} + +@keyframes deplete { + from { + width: 100%; + } + + to { + width: 0%; + } +} diff --git a/Shogi/FrontEnd/Components/Pages/Play/GameBrowser.razor b/Shogi/FrontEnd/Components/Pages/Play/GameBrowser.razor index 7866211..63028cf 100644 --- a/Shogi/FrontEnd/Components/Pages/Play/GameBrowser.razor +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBrowser.razor @@ -12,10 +12,13 @@
- @foreach (var session in allSessions) + @foreach (var session in allSessions) { - + } @@ -29,6 +32,9 @@ @code { private SessionMetadata[] allSessions = Array.Empty(); + [Parameter] public SessionMetadata? SelectedSession { get; set; } + [Parameter] public EventCallback OnSessionSelected { get; set; } + protected override Task OnInitializedAsync() { return FetchSessions(); @@ -44,4 +50,9 @@ StateHasChanged(); } } + + private async Task HandleSessionSelected(SessionMetadata session) + { + await OnSessionSelected.InvokeAsync(session); + } } diff --git a/Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor b/Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor index ab69b0a..b5b65d6 100644 --- a/Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor @@ -1,31 +1,31 @@ @inject ShogiService Service @using System.Security.Claims - - @if (showDeletePrompt) - { - -
- @if (showDeleteError) - { -

An error occurred.

-
- - } - else - { -

Do you wish to delete this session?

-
- - - } -
- - } + +@if (showDeletePrompt) +{ + +
+@if (showDeleteError) +{ +

An error occurred.

+
+ +} +else +{ +

Do you wish to delete this session?

+
+ + +} +
+ +} - +
+@Session.Player1 +
@if (string.IsNullOrEmpty(Session.Player2)) { 1 / 2 @@ -50,9 +50,16 @@ [Parameter][EditorRequired] public SessionMetadata Session { get; set; } = default!; [Parameter][EditorRequired] public EventCallback OnSessionDeleted { get; set; } + [Parameter] public EventCallback OnSessionSelected { get; set; } + [Parameter] public bool IsSelected { get; set; } private bool showDeletePrompt = false; private bool showDeleteError = false; + private async Task HandleSessionSelectedClick() + { + await OnSessionSelected.InvokeAsync(Session); + } + void HideModal() { diff --git a/Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor.css b/Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor.css index 9c18c06..b00c83f 100644 --- a/Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor.css +++ b/Shogi/FrontEnd/Components/Pages/Play/GameBrowserEntry.razor.css @@ -5,8 +5,18 @@ padding-left: 5px; /* Matches box shadow on hover */ gap: 1rem; place-items: center start; + cursor: pointer; } + gameBrowserEntry:hover { + background-color: rgba(255, 255, 255, 0.1); + } + + gameBrowserEntry[data-selected="true"] { + background-color: rgba(255, 255, 255, 0.2); + box-shadow: inset 3px 0 0 0 var(--primary-color, #fff); + } + modal { position: absolute; top: 0; diff --git a/Shogi/FrontEnd/Components/Pages/SearchPage.razor b/Shogi/FrontEnd/Components/Pages/SearchPage.razor index 9d3afd5..d957021 100644 --- a/Shogi/FrontEnd/Components/Pages/SearchPage.razor +++ b/Shogi/FrontEnd/Components/Pages/SearchPage.razor @@ -1,11 +1,57 @@ @page "/search" +@inject ShogiService Service +@inject NavigationManager Navigation +

Find Sessions

- +
+ + + +
@code { + private SessionMetadata? selectedSession; + private Session? previewSession; + private async Task HandleSessionSelected(SessionMetadata session) + { + selectedSession = session; + previewSession = null; + + previewSession = await Service.GetSession(session.SessionId.ToString()); + } + + private void JoinGame() + { + if (selectedSession is not null) + { + Navigation.NavigateTo($"/play/{selectedSession.SessionId}"); + } + } } diff --git a/Shogi/FrontEnd/Components/Pages/SearchPage.razor.css b/Shogi/FrontEnd/Components/Pages/SearchPage.razor.css index f14027d..58cbf6f 100644 --- a/Shogi/FrontEnd/Components/Pages/SearchPage.razor.css +++ b/Shogi/FrontEnd/Components/Pages/SearchPage.razor.css @@ -1,3 +1,39 @@ .SearchPage { padding: 0 0.5rem; + display: grid; + grid-template-columns: 100%; + grid-template-rows: auto 1fr; +} + +.search-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + align-items: start; +} + +.preview-panel { + padding: 1rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + min-height: 300px; +} + +.preview-panel .no-selection { + color: #888; + font-style: italic; +} + +.preview-actions { + display: flex; + gap: 1rem; + justify-content: center; + margin-top: 1rem; +} + +.preview-actions button { + padding: 0.5rem 1.5rem; + font-size: 1rem; } diff --git a/Shogi/wwwroot/css/themes.css b/Shogi/wwwroot/css/themes.css index 216e2e7..ea72a42 100644 --- a/Shogi/wwwroot/css/themes.css +++ b/Shogi/wwwroot/css/themes.css @@ -3,6 +3,7 @@ --middlegroundColor: #D1D1D1; --foregroundColor: #eaeaea; --hrefColor: #99c3ff; + --middlegroundHrefColor: #0065be; --uniformBottomMargin: 0.5rem; background-color: var(--backgroundColor); color: var(--foregroundColor);