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.
Compte Keycloak relie a une fiche site compacte stockee en MySQL.
Cree@FormatDate(Profile.CreatedUtc)Mis a jour@FormatDate(Profile.UpdatedUtc)Club@(Profile.Club ?? "A definir")Ville@(Profile.City ?? "A definir")Format@(Profile.PreferredFormat ?? "A definir")Cube favori@(Profile.FavoriteCube ?? "A definir")
Bio
@(Profile.Bio ?? "Aucune bio enregistree pour le moment.")
@if (!string.IsNullOrWhiteSpace(SaveError))
{
@SaveError
}
@if (!string.IsNullOrWhiteSpace(SaveMessage))
{
@SaveMessage
}
}
@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;
}
}
}