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.
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.UsernameEmail@(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;
}
}
}