@page "/utilisateur" @page "/utilisateur.html" @using System.ComponentModel.DataAnnotations @using System.Net @using System.Net.Http.Json @using ChessCubing.App.Models.Users @implements IDisposable @inject AuthenticationStateProvider AuthenticationStateProvider @inject HttpClient Http ChessCubing Arena | Utilisateur

Espace utilisateur

Gerer les donnees du site pour chaque joueur

Cette page relie le compte connecte a une fiche utilisateur stockee cote serveur en MySQL. L'authentification reste geree par Keycloak, mais les informations metier du site sont maintenant pretes pour des evolutions futures.

@if (!IsAuthenticated) {

Connexion requise

Connecte-toi pour gerer ton profil

Utilise les boutons Se connecter ou Creer un compte dans le menu en haut de page, puis reviens ici pour enregistrer tes informations.

} else if (IsLoading) {

Chargement

Recuperation du profil utilisateur

Le serveur recharge les donnees enregistrees pour ce compte.

} else if (!string.IsNullOrWhiteSpace(LoadError)) {

Serveur

Impossible de charger le profil

@LoadError

} else if (Profile is not null) {

Compte relie

@Profile.DisplayName

Le compte connecte pilote l'identite, et cette page ajoute les donnees metier du site.

Username @Profile.Username
Email @(Profile.Email ?? "Non renseigne")
Cree le @FormatDate(Profile.CreatedUtc)
Mis a jour @FormatDate(Profile.UpdatedUtc)

Resume du profil

Informations visibles pour le site

Club @(Profile.Club ?? "A definir")
Ville @(Profile.City ?? "A definir")
Format prefere @(Profile.PreferredFormat ?? "A definir")
Cube favori @(Profile.FavoriteCube ?? "A definir")
Bio

@(Profile.Bio ?? "Aucune bio enregistree pour le moment.")

Edition

Mettre a jour le profil du site

Les champs ci-dessous sont stockes dans MySQL et pourront ensuite servir a enrichir les pages, les inscriptions ou les statistiques.

@if (!string.IsNullOrWhiteSpace(SaveError)) {

@SaveError

} @if (!string.IsNullOrWhiteSpace(SaveMessage)) {

@SaveMessage

}

Les champs vides restent facultatifs et peuvent etre completes plus tard.

}
@code { private readonly UserProfileFormModel Form = new(); private UserProfileResponse? Profile; private bool IsAuthenticated; private bool IsLoading = true; private bool IsSaving; private string? LoadError; private string? SaveError; private string? SaveMessage; private string HeroStatusTitle => !IsAuthenticated ? "Connexion requise" : IsLoading ? "Chargement du profil" : Profile?.DisplayName ?? "Profil utilisateur"; private string HeroStatusDescription => !IsAuthenticated ? "Le profil du site apparait des qu'un compte joueur est connecte." : IsLoading ? "Le serveur verifie la fiche utilisateur associee a ce compte." : $"Compte lie a {Profile?.Username ?? "l'utilisateur connecte"} et stocke en base MySQL."; protected override async Task OnInitializedAsync() { AuthenticationStateProvider.AuthenticationStateChanged += HandleAuthenticationStateChanged; await LoadProfileAsync(); } private void HandleAuthenticationStateChanged(Task authenticationStateTask) => _ = InvokeAsync(LoadProfileAsync); private async Task LoadProfileAsync() { LoadError = null; SaveError = null; SaveMessage = null; IsLoading = true; try { var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); if (authState.User.Identity?.IsAuthenticated != true) { ResetProfileState(); return; } IsAuthenticated = true; var response = await Http.GetAsync("api/users/me"); if (response.StatusCode == HttpStatusCode.Unauthorized) { ResetProfileState(); return; } if (!response.IsSuccessStatusCode) { LoadError = await ReadErrorAsync(response, "Le profil utilisateur n'a pas pu etre charge."); Profile = null; return; } Profile = await response.Content.ReadFromJsonAsync(); if (Profile is null) { LoadError = "Le serveur a retourne une reponse vide."; return; } FillForm(Profile); } catch (HttpRequestException) { LoadError = "Le service utilisateur est temporairement indisponible."; Profile = null; } catch (TaskCanceledException) { LoadError = "La reponse du service utilisateur a pris trop de temps."; Profile = null; } finally { IsLoading = false; StateHasChanged(); } } private async Task SaveProfileAsync() { if (IsSaving || !IsAuthenticated) { return; } IsSaving = true; SaveError = null; SaveMessage = null; try { var payload = new UpdateUserProfileRequest { DisplayName = Form.DisplayName, Club = Form.Club, City = Form.City, PreferredFormat = Form.PreferredFormat, FavoriteCube = Form.FavoriteCube, Bio = Form.Bio, }; var response = await Http.PutAsJsonAsync("api/users/me", payload); if (!response.IsSuccessStatusCode) { SaveError = await ReadErrorAsync(response, "L'enregistrement du profil a echoue."); return; } Profile = await response.Content.ReadFromJsonAsync(); if (Profile is null) { SaveError = "Le serveur a retourne une reponse vide apres la sauvegarde."; return; } FillForm(Profile); SaveMessage = "Le profil utilisateur a bien ete enregistre."; } catch (HttpRequestException) { SaveError = "Le service utilisateur est temporairement indisponible."; } catch (TaskCanceledException) { SaveError = "La sauvegarde a pris trop de temps."; } finally { IsSaving = false; } } private void ResetProfileState() { IsAuthenticated = false; Profile = null; Form.Reset(); } private void FillForm(UserProfileResponse profile) { Form.DisplayName = profile.DisplayName; Form.Club = profile.Club; Form.City = profile.City; Form.PreferredFormat = profile.PreferredFormat; Form.FavoriteCube = profile.FavoriteCube; Form.Bio = profile.Bio; } private static async Task ReadErrorAsync(HttpResponseMessage response, string fallbackMessage) { try { var error = await response.Content.ReadFromJsonAsync(); if (!string.IsNullOrWhiteSpace(error?.Message)) { return error.Message; } } catch { } return response.StatusCode switch { HttpStatusCode.Unauthorized => "La session a expire. Reconnecte-toi puis recharge la page.", _ => fallbackMessage, }; } private static string FormatDate(DateTime value) => value.ToLocalTime().ToString("dd MMM yyyy 'a' HH:mm", CultureInfo.GetCultureInfo("fr-FR")); public void Dispose() => AuthenticationStateProvider.AuthenticationStateChanged -= HandleAuthenticationStateChanged; private sealed class ApiErrorMessage { public string? Message { get; set; } } private sealed class UserProfileFormModel { [MaxLength(120, ErrorMessage = "Le nom affiche doit rester sous 120 caracteres.")] public string? DisplayName { get; set; } [MaxLength(120, ErrorMessage = "Le club doit rester sous 120 caracteres.")] public string? Club { get; set; } [MaxLength(120, ErrorMessage = "La ville doit rester sous 120 caracteres.")] public string? City { get; set; } [MaxLength(40, ErrorMessage = "Le format prefere doit rester sous 40 caracteres.")] public string? PreferredFormat { get; set; } [MaxLength(120, ErrorMessage = "Le cube favori doit rester sous 120 caracteres.")] public string? FavoriteCube { get; set; } [MaxLength(1200, ErrorMessage = "La bio doit rester sous 1200 caracteres.")] public string? Bio { get; set; } public void Reset() { DisplayName = string.Empty; Club = string.Empty; City = string.Empty; PreferredFormat = string.Empty; FavoriteCube = string.Empty; Bio = string.Empty; } } }