281 lines
13 KiB
Plaintext
281 lines
13 KiB
Plaintext
@page "/profile"
|
||
@rendermode InteractiveWebAssembly
|
||
@attribute [Authorize]
|
||
@using System.Security.Claims
|
||
@inject IAuthService AuthService
|
||
@inject ITimetrackerService TrackerService
|
||
@inject ISnackbar Snackbar
|
||
@inject AuthenticationStateProvider AuthStateProvider
|
||
@inject NavigationManager Nav
|
||
|
||
<PageTitle>Benutzerprofil – Timetracker</PageTitle>
|
||
|
||
@if (_loading)
|
||
{
|
||
<MudStack AlignItems="AlignItems.Center" Class="mt-16">
|
||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
|
||
<MudText Color="Color.Secondary">Lade Profil…</MudText>
|
||
</MudStack>
|
||
}
|
||
else
|
||
{
|
||
<MudStack Spacing="4">
|
||
|
||
@* ── Header ── *@
|
||
<MudPaper Elevation="4" Class="pa-5 rounded-xl mud-theme-primary">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||
<MudIcon Icon="@Icons.Material.Filled.AccountCircle" Style="color: var(--mud-palette-primary-text); font-size:2.5rem" />
|
||
<MudStack Spacing="0">
|
||
<MudText Typo="Typo.h5" Style="color: var(--mud-palette-primary-text); font-weight:700">Mein Profil</MudText>
|
||
<MudText Typo="Typo.caption" Style="color: var(--mud-palette-primary-text); opacity: 0.72;">
|
||
Persönliche Details verwalten und Account sichern
|
||
</MudText>
|
||
</MudStack>
|
||
</MudStack>
|
||
</MudPaper>
|
||
|
||
<MudGrid Spacing="4">
|
||
|
||
@* ── Übersicht & Details ── *@
|
||
<MudItem xs="12" md="4">
|
||
<MudCard Elevation="3" Class="rounded-xl h-100">
|
||
<MudCardContent>
|
||
<MudStack AlignItems="AlignItems.Center" Class="py-6" Spacing="3">
|
||
<MudAvatar Style="width:96px; height:96px; font-size:2rem; font-weight:700; background: linear-gradient(135deg, #0EA5E9 0%, #0284C7 100%); color: white;">
|
||
@GetInitials(_username)
|
||
</MudAvatar>
|
||
<MudStack AlignItems="AlignItems.Center" Spacing="0">
|
||
<MudText Typo="Typo.h6" Style="font-weight:700">@_username</MudText>
|
||
<MudText Typo="Typo.caption" Color="Color.Secondary">@(_isAdmin ? "Administrator" : "Standardbenutzer")</MudText>
|
||
</MudStack>
|
||
</MudStack>
|
||
|
||
<MudDivider Class="my-3" />
|
||
|
||
<MudStack Spacing="2" Class="pa-2">
|
||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||
<MudText Typo="Typo.body2" Color="Color.Secondary">Benutzer-ID</MudText>
|
||
<MudText Typo="Typo.body2"><b>#@_userId</b></MudText>
|
||
</MudStack>
|
||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||
<MudText Typo="Typo.body2" Color="Color.Secondary">Gleitzeit-Saldo</MudText>
|
||
<MudText Typo="Typo.body2" Color="@(_overtime >= TimeSpan.Zero ? Color.Success : Color.Warning)">
|
||
<b>@FormatTs(_overtime, sign: true)</b>
|
||
</MudText>
|
||
</MudStack>
|
||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||
<MudText Typo="Typo.body2" Color="Color.Secondary">Sollstunden / Tag</MudText>
|
||
<MudText Typo="Typo.body2"><b>@_settings.DailyTargetHours h</b></MudText>
|
||
</MudStack>
|
||
</MudStack>
|
||
</MudCardContent>
|
||
</MudCard>
|
||
</MudItem>
|
||
|
||
@* ── Einstellungen verwalten ── *@
|
||
<MudItem xs="12" md="8">
|
||
<MudStack Spacing="4">
|
||
|
||
@* Benutzername ändern *@
|
||
<MudCard Elevation="3" Class="rounded-xl">
|
||
<MudCardHeader>
|
||
<CardHeaderContent>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.Badge" Color="Color.Primary" />
|
||
<MudText Typo="Typo.h6" Style="font-weight:600">Benutzername ändern</MudText>
|
||
</MudStack>
|
||
</CardHeaderContent>
|
||
</MudCardHeader>
|
||
<MudCardContent>
|
||
<MudForm @ref="_renameForm" @bind-IsValid="_renameIsValid">
|
||
<MudStack Spacing="3">
|
||
<MudTextField @bind-Value="_newUsername"
|
||
Label="Neuer Benutzername"
|
||
Variant="Variant.Outlined"
|
||
Required="true"
|
||
RequiredError="Bitte gib einen Benutzernamen ein."
|
||
Validation="@(new Func<string, string?>(ValidateUsername))" />
|
||
<MudButton Variant="Variant.Filled"
|
||
Color="Color.Primary"
|
||
OnClick="HandleRename"
|
||
Disabled="!_renameIsValid || _newUsername == _username"
|
||
Class="align-self-start rounded-lg py-2 px-4"
|
||
StartIcon="@Icons.Material.Filled.Save">
|
||
Speichern
|
||
</MudButton>
|
||
</MudStack>
|
||
</MudForm>
|
||
</MudCardContent>
|
||
</MudCard>
|
||
|
||
@* Passwort ändern *@
|
||
<MudCard Elevation="3" Class="rounded-xl">
|
||
<MudCardHeader>
|
||
<CardHeaderContent>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.Lock" Color="Color.Primary" />
|
||
<MudText Typo="Typo.h6" Style="font-weight:600">Passwort ändern</MudText>
|
||
</MudStack>
|
||
</CardHeaderContent>
|
||
</MudCardHeader>
|
||
<MudCardContent>
|
||
<MudForm @ref="_passwordForm" @bind-IsValid="_passwordIsValid">
|
||
<MudStack Spacing="3">
|
||
<MudTextField @bind-Value="_currentPassword"
|
||
Label="Aktuelles Passwort"
|
||
InputType="InputType.Password"
|
||
Variant="Variant.Outlined"
|
||
Required="true"
|
||
RequiredError="Bitte gib dein aktuelles Passwort ein." />
|
||
<MudTextField @bind-Value="_newPassword"
|
||
Label="Neues Passwort"
|
||
InputType="InputType.Password"
|
||
Variant="Variant.Outlined"
|
||
Required="true"
|
||
RequiredError="Bitte gib dein neues Passwort ein."
|
||
Validation="@(new Func<string, string?>(ValidatePassword))" />
|
||
<MudTextField @bind-Value="_confirmPassword"
|
||
Label="Neues Passwort bestätigen"
|
||
InputType="InputType.Password"
|
||
Variant="Variant.Outlined"
|
||
Required="true"
|
||
RequiredError="Bitte bestätige dein neues Passwort."
|
||
Validation="@(new Func<string, string?>(ValidateConfirmPassword))" />
|
||
<MudButton Variant="Variant.Filled"
|
||
Color="Color.Primary"
|
||
OnClick="HandleChangePassword"
|
||
Disabled="!_passwordIsValid"
|
||
Class="align-self-start rounded-lg py-2 px-4"
|
||
StartIcon="@Icons.Material.Filled.VpnKey">
|
||
Passwort ändern
|
||
</MudButton>
|
||
</MudStack>
|
||
</MudForm>
|
||
</MudCardContent>
|
||
</MudCard>
|
||
|
||
</MudStack>
|
||
</MudItem>
|
||
|
||
</MudGrid>
|
||
|
||
</MudStack>
|
||
}
|
||
|
||
@code {
|
||
private bool _loading = true;
|
||
private int _userId;
|
||
private string _username = "";
|
||
private string _newUsername = "";
|
||
private bool _isAdmin;
|
||
private AppSettings _settings = new();
|
||
private TimeSpan _overtime;
|
||
|
||
// Formulare
|
||
private MudForm? _renameForm;
|
||
private bool _renameIsValid;
|
||
private MudForm? _passwordForm;
|
||
private bool _passwordIsValid;
|
||
|
||
// Passwörter
|
||
private string _currentPassword = "";
|
||
private string _newPassword = "";
|
||
private string _confirmPassword = "";
|
||
|
||
protected override async Task OnInitializedAsync()
|
||
{
|
||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
|
||
if (claim == null) return;
|
||
|
||
_userId = int.Parse(claim.Value);
|
||
_username = authState.User.Identity?.Name ?? "";
|
||
_newUsername = _username;
|
||
_isAdmin = _username.Equals("marc", StringComparison.OrdinalIgnoreCase);
|
||
|
||
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||
_overtime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
|
||
_loading = false;
|
||
}
|
||
|
||
private string GetInitials(string? name)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(name)) return "U";
|
||
var parts = name.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||
if (parts.Length == 1) return parts[0][..Math.Min(2, parts[0].Length)].ToUpper();
|
||
return $"{parts[0][0]}{parts[1][0]}".ToUpper();
|
||
}
|
||
|
||
private static string FormatTs(TimeSpan ts, bool sign = false)
|
||
{
|
||
if (ts == TimeSpan.Zero && sign) return "±0:00";
|
||
var prefix = sign ? (ts >= TimeSpan.Zero ? "+" : "−") : (ts < TimeSpan.Zero ? "−" : "");
|
||
var abs = ts.Duration();
|
||
return $"{prefix}{(int)abs.TotalHours}:{abs.Minutes:D2}";
|
||
}
|
||
|
||
// Validierungen
|
||
private string? ValidateUsername(string val)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(val) || val.Length < 3)
|
||
return "Benutzername muss mindestens 3 Zeichen lang sein.";
|
||
return null;
|
||
}
|
||
|
||
private string? ValidatePassword(string val)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(val) || val.Length < 6)
|
||
return "Passwort muss mindestens 6 Zeichen lang sein.";
|
||
return null;
|
||
}
|
||
|
||
private string? ValidateConfirmPassword(string val)
|
||
{
|
||
if (val != _newPassword)
|
||
return "Die Passwörter stimmen nicht überein.";
|
||
return null;
|
||
}
|
||
|
||
// Aktionen
|
||
private async Task HandleRename()
|
||
{
|
||
if (_renameForm == null) return;
|
||
await _renameForm.ValidateAsync();
|
||
if (!_renameForm.IsValid) return;
|
||
|
||
var error = await AuthService.RenameUserAsync(_userId, _newUsername);
|
||
if (error != null)
|
||
{
|
||
Snackbar.Add(error, Severity.Error);
|
||
}
|
||
else
|
||
{
|
||
Snackbar.Add("Benutzername erfolgreich geändert. Bitte melde dich ggf. neu an, um alle Anzeigen zu aktualisieren.", Severity.Success);
|
||
_username = _newUsername;
|
||
// Umleitung zur Startseite, um die neue Identity zu laden
|
||
Nav.NavigateTo("/", forceLoad: true);
|
||
}
|
||
}
|
||
|
||
private async Task HandleChangePassword()
|
||
{
|
||
if (_passwordForm == null) return;
|
||
await _passwordForm.ValidateAsync();
|
||
if (!_passwordForm.IsValid) return;
|
||
|
||
var error = await AuthService.ChangePasswordAsync(_userId, _currentPassword, _newPassword);
|
||
if (error != null)
|
||
{
|
||
Snackbar.Add(error, Severity.Error);
|
||
}
|
||
else
|
||
{
|
||
Snackbar.Add("Passwort erfolgreich geändert.", Severity.Success);
|
||
_currentPassword = "";
|
||
_newPassword = "";
|
||
_confirmPassword = "";
|
||
if (_passwordForm != null) await _passwordForm.ResetAsync();
|
||
}
|
||
}
|
||
}
|