checkpoint
This commit is contained in:
@@ -3,9 +3,9 @@
|
||||
@inject NavigationManager navigator
|
||||
|
||||
<main class="PrimaryTheme">
|
||||
<h1>Login</h1>
|
||||
|
||||
<section class="LoginForm">
|
||||
<form class="LoginForm" @onsubmit="HandleSubmit">
|
||||
<h1 style="grid-area: h1">Login</h1>
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<div>You're logged in as @context.User.Identity?.Name.</div>
|
||||
@@ -13,26 +13,32 @@
|
||||
<NotAuthorized>
|
||||
@if (errorList.Length > 0)
|
||||
{
|
||||
<ul class="Errors" style="grid-area: errors">
|
||||
<div class="Errors" style="grid-area: errors">
|
||||
<ul>
|
||||
@foreach (var error in errorList)
|
||||
{
|
||||
<li>@error</li>
|
||||
}
|
||||
</ul>
|
||||
<div class="ErrorProgress" @key="errorKey"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<label for="email" style="grid-area: emailLabel">Email</label>
|
||||
<input required id="email" name="emailInput" type="email" style="grid-area: emailControl" @bind-value="email" />
|
||||
<input required id="email" name="emailInput" type="email" style="grid-area: emailControl" @bind-value="email" readonly="@isEmailSubmitted" />
|
||||
|
||||
@if (isEmailSubmitted)
|
||||
{
|
||||
<label for="password" style="grid-area: passLabel">Password</label>
|
||||
<input required id="password" name="passwordInput" type="password" style="grid-area: passControl" @bind-value="password" />
|
||||
|
||||
<a href="forgot" style="grid-area: resetLink; place-self: end;">Reset password</a>
|
||||
}
|
||||
|
||||
<button style="grid-area: button" @onclick="DoLoginAsync">Login</button>
|
||||
<button style="grid-area: button" type="submit">@(isEmailSubmitted ? "Login" : "Next")</button>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
</main>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
@foreach (var session in allSessions)
|
||||
{
|
||||
<row>
|
||||
<GameBrowserEntry Session="session" OnSessionDeleted="FetchSessions" />
|
||||
<GameBrowserEntry Session="session"
|
||||
OnSessionDeleted="FetchSessions"
|
||||
OnSessionSelected="HandleSessionSelected"
|
||||
IsSelected="SelectedSession?.SessionId == session.SessionId" />
|
||||
</row>
|
||||
}
|
||||
</div>
|
||||
@@ -29,6 +32,9 @@
|
||||
@code {
|
||||
private SessionMetadata[] allSessions = Array.Empty<SessionMetadata>();
|
||||
|
||||
[Parameter] public SessionMetadata? SelectedSession { get; set; }
|
||||
[Parameter] public EventCallback<SessionMetadata> OnSessionSelected { get; set; }
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
return FetchSessions();
|
||||
@@ -44,4 +50,9 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleSessionSelected(SessionMetadata session)
|
||||
{
|
||||
await OnSessionSelected.InvokeAsync(session);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@inject ShogiService Service
|
||||
@using System.Security.Claims
|
||||
|
||||
<gameBrowserEntry>
|
||||
<gameBrowserEntry @onclick="HandleSessionSelectedClick" data-selected="@IsSelected">
|
||||
@if (showDeletePrompt)
|
||||
{
|
||||
<modal class="PrimaryTheme ThemeVariant--Contrast">
|
||||
@@ -24,7 +24,7 @@
|
||||
}
|
||||
|
||||
<div>
|
||||
<a href="play/@Session.SessionId">@Session.Player1</a>
|
||||
<span>@Session.Player1</span>
|
||||
</div>
|
||||
@if (string.IsNullOrEmpty(Session.Player2))
|
||||
{
|
||||
@@ -50,9 +50,16 @@
|
||||
|
||||
[Parameter][EditorRequired] public SessionMetadata Session { get; set; } = default!;
|
||||
[Parameter][EditorRequired] public EventCallback OnSessionDeleted { get; set; }
|
||||
[Parameter] public EventCallback<SessionMetadata> 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()
|
||||
{
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
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 {
|
||||
|
||||
@@ -1,11 +1,57 @@
|
||||
@page "/search"
|
||||
|
||||
@inject ShogiService Service
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<main class="SearchPage PrimaryTheme">
|
||||
<h3>Find Sessions</h3>
|
||||
|
||||
<GameBrowser />
|
||||
<div class="search-content">
|
||||
<GameBrowser SelectedSession="selectedSession" OnSessionSelected="HandleSessionSelected" />
|
||||
|
||||
<aside class="preview-panel PrimaryTheme ThemeVariant--Contrast">
|
||||
@if (selectedSession is not null)
|
||||
{
|
||||
@if (previewSession is not null)
|
||||
{
|
||||
<GameBoardPresentation Session="previewSession"
|
||||
Perspective="WhichPlayer.Player1"
|
||||
UseSideboard="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>Loading preview...</p>
|
||||
}
|
||||
|
||||
<div class="preview-actions">
|
||||
<button @onclick="JoinGame">Join Game</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="no-selection">Select a game to preview</p>
|
||||
}
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
--middlegroundColor: #D1D1D1;
|
||||
--foregroundColor: #eaeaea;
|
||||
--hrefColor: #99c3ff;
|
||||
--middlegroundHrefColor: #0065be;
|
||||
--uniformBottomMargin: 0.5rem;
|
||||
background-color: var(--backgroundColor);
|
||||
color: var(--foregroundColor);
|
||||
|
||||
Reference in New Issue
Block a user