diff --git a/ChessCubing.App/Pages/ApplicationPage.razor b/ChessCubing.App/Pages/ApplicationPage.razor index eba147b..11eebd3 100644 --- a/ChessCubing.App/Pages/ApplicationPage.razor +++ b/ChessCubing.App/Pages/ApplicationPage.razor @@ -1,8 +1,15 @@ @page "/application" @page "/application.html" +@using System.Net +@using System.Net.Http.Json +@using System.Security.Claims +@using ChessCubing.App.Models.Users +@implements IDisposable @inject BrowserBridge Browser @inject MatchStore Store @inject NavigationManager Navigation +@inject AuthenticationStateProvider AuthenticationStateProvider +@inject HttpClient Http ChessCubing Arena | Application @@ -137,13 +144,39 @@ - - Joueur blanc + + + Joueur blanc + + @if (CanUseConnectedPlayerName) + { + + account_circle + + } + - - Joueur noir + + + Joueur noir + + @if (CanUseConnectedPlayerName) + { + + account_circle + + } + @@ -254,10 +287,12 @@ @code { private SetupFormModel Form { get; set; } = new(); private bool _ready; + private string? ConnectedPlayerName; private MatchState? CurrentMatch => Store.Current; private string SetupBodyClass => UsesMoveLimit ? string.Empty : "time-setup-mode"; + private bool CanUseConnectedPlayerName => !string.IsNullOrWhiteSpace(ConnectedPlayerName); private bool UsesMoveLimit => MatchEngine.UsesMoveLimit(Form.Mode); @@ -286,6 +321,12 @@ ? $"Quota actif : {CurrentPreset.Quota} coups par joueur." : $"Quota actif : {CurrentPreset.Quota} coups par joueur et par Block."; + protected override async Task OnInitializedAsync() + { + AuthenticationStateProvider.AuthenticationStateChanged += HandleAuthenticationStateChanged; + await LoadConnectedPlayerAsync(); + } + protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) @@ -298,6 +339,51 @@ StateHasChanged(); } + private void HandleAuthenticationStateChanged(Task authenticationStateTask) + => _ = InvokeAsync(LoadConnectedPlayerAsync); + + private async Task LoadConnectedPlayerAsync() + { + string? fallbackName = null; + + try + { + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + + if (user.Identity?.IsAuthenticated != true) + { + ConnectedPlayerName = null; + await InvokeAsync(StateHasChanged); + return; + } + + fallbackName = BuildConnectedPlayerFallback(user); + + var response = await Http.GetAsync("api/users/me"); + if (!response.IsSuccessStatusCode) + { + ConnectedPlayerName = response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden + ? null + : fallbackName; + + await InvokeAsync(StateHasChanged); + return; + } + + var profile = await response.Content.ReadFromJsonAsync(); + ConnectedPlayerName = !string.IsNullOrWhiteSpace(profile?.DisplayName) + ? profile.DisplayName + : fallbackName; + } + catch + { + ConnectedPlayerName = fallbackName; + } + + await InvokeAsync(StateHasChanged); + } + private async Task HandleSubmit() { await Store.EnsureLoadedAsync(); @@ -320,6 +406,26 @@ private void LoadDemo() => Form = SetupFormModel.CreateDemo(); + private void FillWhiteWithConnectedPlayer() + { + if (!CanUseConnectedPlayerName) + { + return; + } + + Form.WhiteName = ConnectedPlayerName!; + } + + private void FillBlackWithConnectedPlayer() + { + if (!CanUseConnectedPlayerName) + { + return; + } + + Form.BlackName = ConnectedPlayerName!; + } + private void SetMode(string mode) => Form.Mode = mode; @@ -360,4 +466,20 @@ return match.Phase == MatchEngine.PhaseCube ? "Page cube prete" : "Page chrono prete"; } + + private string BuildPrefillTitle(string color) + => $"Utiliser mon nom cote {color}"; + + private static string? BuildConnectedPlayerFallback(ClaimsPrincipal user) + => FirstNonEmpty( + user.FindFirst("name")?.Value, + user.FindFirst(ClaimTypes.Name)?.Value, + user.FindFirst("preferred_username")?.Value, + user.FindFirst(ClaimTypes.Email)?.Value); + + private static string? FirstNonEmpty(params string?[] candidates) + => candidates.FirstOrDefault(candidate => !string.IsNullOrWhiteSpace(candidate)); + + public void Dispose() + => AuthenticationStateProvider.AuthenticationStateChanged -= HandleAuthenticationStateChanged; } diff --git a/styles.css b/styles.css index cfdab86..bb42609 100644 --- a/styles.css +++ b/styles.css @@ -414,6 +414,17 @@ p { gap: 0.45rem; } +.field-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.65rem; +} + +.field-label-text { + font-weight: 700; +} + [hidden] { display: none !important; } @@ -567,6 +578,20 @@ textarea:focus { gap: 0.75rem; } +.player-name-field .field-heading { + margin-bottom: -0.1rem; +} + +.player-fill-button { + flex: 0 0 auto; + padding: 0.42rem 0.48rem; + border-radius: 12px; +} + +.player-fill-button .action-icon { + font-size: 1.05rem; +} + .side-panel { display: grid; gap: 1rem; @@ -2380,11 +2405,16 @@ body.site-menu-hidden .site-menu-shell { gap: 0.25rem; } + body[data-page="setup"] .field-label-text, body[data-page="setup"] .field > span, body[data-page="setup"] legend { font-size: 0.9rem; } + body[data-page="setup"] .player-fill-button { + padding: 0.36rem 0.42rem; + } + body[data-page="setup"] input, body[data-page="setup"] textarea { padding: 0.75rem 0.85rem; @@ -2751,11 +2781,16 @@ body.site-menu-hidden .site-menu-shell { gap: 0.45rem; } + body[data-page="setup"] .field-label-text, body[data-page="setup"] .field > span, body[data-page="setup"] legend { font-size: 0.84rem; } + body[data-page="setup"] .player-fill-button { + padding: 0.34rem 0.4rem; + } + body[data-page="setup"] input, body[data-page="setup"] textarea { padding: 0.68rem 0.75rem;