add profile page

This commit is contained in:
beo3000 2025-12-23 17:28:14 +01:00
parent ad3ac3185c
commit d26a2cb1c5
3 changed files with 257 additions and 2 deletions

View File

@ -333,7 +333,7 @@ NavMenu.razor:
| ✓ | B1 | User/Account | UserService erweitern | 1 Service, 1 Interface, 1 DTO |
| ✓ | B2 | User/Account | Register Page | 1 Razor |
| ✓ | B3 | User/Account | Password Reset Pages | 2 Razor |
| | B4 | User/Account | Profile Page | 1 Razor |
| | B4 | User/Account | Profile Page | 1 Razor |
| ☐ | B5 | User/Account | Admin Users Page | 1 Razor |
| ☐ | **C1** | **Clubs** | **ClubState Fluxor** | **4 State-Dateien** |
| ☐ | **C2** | **Clubs** | **Club Pages - ERSTES TESTBARES MVP** | **1 Razor** |

View File

@ -2,6 +2,9 @@
@inject IState<AuthState> AuthState
@inject IDispatcher Dispatcher
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery
@inject IHttpContextAccessor HttpContextAccessor
@inject IJSRuntime JS
<AuthStateInitializer />
@ -18,6 +21,18 @@
<MudLayout>
<MudAppBar Elevation="1" Dense="true">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="ToggleDrawer" />
<MudText Typo="Typo.h6">KOOGLE</MudText>
<MudSpacer />
@if (AuthState.Value.IsAuthenticated)
{
<MudMenu Icon="@Icons.Material.Filled.Person" Color="Color.Inherit" AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopRight">
<MudMenuItem Href="/account/profile" Icon="@Icons.Material.Filled.AccountCircle">Profil</MudMenuItem>
<MudDivider />
<MudMenuItem OnClick="Logout" Icon="@Icons.Material.Filled.Logout">Abmelden</MudMenuItem>
</MudMenu>
}
<MudIconButton Icon="@(_isDarkMode ? Icons.Material.Filled.LightMode : Icons.Material.Filled.DarkMode)" Color="Color.Inherit" OnClick="ToggleDarkMode" />
</MudAppBar>
<MudDrawer @bind-Open="_drawerOpen"
@ -51,12 +66,34 @@
<span class="dismiss">🗙</span>
</div>
<!-- Hidden logout form -->
<form id="logout-form" method="post" action="/auth/logout" style="display:none;">
<input type="hidden" name="__RequestVerificationToken" value="@_logoutToken" />
<input type="hidden" name="returnUrl" value="/account/login" />
</form>
@code
{
private bool _drawerOpen = true;
private bool _isDarkMode = false;
private string _logoutToken = "";
protected override void OnInitialized()
{
base.OnInitialized();
var http = HttpContextAccessor.HttpContext;
if (http != null)
{
var tokens = Antiforgery.GetAndStoreTokens(http);
_logoutToken = tokens.RequestToken ?? "";
}
}
private async Task Logout()
{
await JS.InvokeVoidAsync("eval", "document.getElementById('logout-form').submit()");
}
private readonly MudTheme _theme = new()
{

View File

@ -0,0 +1,218 @@
@page "/account/profile"
@attribute [Authorize]
@using Fluxor
@using Koogle.Application.DTOs
@using Koogle.Application.Interfaces
@using Koogle.Web.Store.AuthState
@using Microsoft.AspNetCore.Authorization
@inject IState<AuthState> AuthState
@inject IUserService UserService
@inject ISnackbar Snackbar
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
<PageTitle>Profil</PageTitle>
<MudContainer MaxWidth="MaxWidth.Small" Class="mt-4">
<MudText Typo="Typo.h4" Class="mb-4">Mein Profil</MudText>
@if (_isLoading)
{
<MudProgressCircular Indeterminate="true" />
}
else if (_user == null)
{
<MudAlert Severity="Severity.Warning">Benutzer nicht gefunden.</MudAlert>
}
else
{
<MudPaper Class="pa-4 mb-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Kontoinformationen</MudText>
<MudTextField @bind-Value="_displayName"
Label="Anzeigename"
Variant="Variant.Outlined"
Class="mb-3" />
<MudTextField Value="@_user.Identity.Email"
Label="E-Mail"
Variant="Variant.Outlined"
ReadOnly="true"
Disabled="true"
Class="mb-3" />
<MudSelect @bind-Value="_locale"
Label="Sprache"
Variant="Variant.Outlined"
Class="mb-3">
<MudSelectItem Value="@("de-DE")">Deutsch</MudSelectItem>
<MudSelectItem Value="@("en-US")">English</MudSelectItem>
</MudSelect>
<MudSelect @bind-Value="_timeZone"
Label="Zeitzone"
Variant="Variant.Outlined"
Class="mb-4">
<MudSelectItem Value="@("Europe/Berlin")">Europe/Berlin</MudSelectItem>
<MudSelectItem Value="@("Europe/Vienna")">Europe/Vienna</MudSelectItem>
<MudSelectItem Value="@("Europe/Zurich")">Europe/Zurich</MudSelectItem>
<MudSelectItem Value="@("UTC")">UTC</MudSelectItem>
</MudSelect>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="SaveProfile"
Disabled="_isSaving">
@if (_isSaving)
{
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
}
Speichern
</MudButton>
</MudPaper>
<MudPaper Class="pa-4 mb-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Club-Mitgliedschaften</MudText>
@if (_user.ClubMemberships.Count == 0)
{
<MudAlert Severity="Severity.Info">Keine Club-Mitgliedschaften vorhanden.</MudAlert>
}
else
{
<MudTable Items="_user.ClubMemberships" Dense="true" Hover="true">
<HeaderContent>
<MudTh>Club</MudTh>
<MudTh>Rollen</MudTh>
<MudTh>Standard</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Club">@context.ClubName</MudTd>
<MudTd DataLabel="Rollen">
@foreach (var role in context.Roles)
{
<MudChip T="string" Size="Size.Small" Color="Color.Primary" Class="mr-1">@role</MudChip>
}
</MudTd>
<MudTd DataLabel="Standard">
@if (context.IsDefault)
{
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" />
}
else
{
<MudButton Size="Size.Small"
Variant="Variant.Text"
OnClick="@(() => SetDefaultClub(context.ClubId))"
Disabled="_isSaving">
Als Standard
</MudButton>
}
</MudTd>
</RowTemplate>
</MudTable>
}
</MudPaper>
<MudPaper Class="pa-4" Elevation="2">
<MudText Typo="Typo.h6" Class="mb-3">Sicherheit</MudText>
<MudButton Href="/account/forgot-password"
Variant="Variant.Outlined"
Color="Color.Primary">
Passwort aendern
</MudButton>
</MudPaper>
}
</MudContainer>
@code {
private UserDto? _user;
private string _displayName = "";
private string _locale = "de-DE";
private string _timeZone = "Europe/Berlin";
private bool _isLoading = true;
private bool _isSaving;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await LoadUser();
}
private async Task LoadUser()
{
_isLoading = true;
try
{
_user = await UserService.GetCurrentUserAsync();
if (_user != null)
{
_displayName = _user.DisplayName;
_locale = _user.Locale ?? "de-DE";
_timeZone = _user.TimeZone ?? "Europe/Berlin";
}
}
finally
{
_isLoading = false;
}
}
private async Task SaveProfile()
{
_isSaving = true;
try
{
var dto = new UpdateUserProfileDto
{
DisplayName = _displayName,
Locale = _locale,
TimeZone = _timeZone
};
var result = await UserService.UpdateProfileAsync(dto);
if (result != null)
{
_user = result;
Snackbar.Add("Profil gespeichert", Severity.Success);
}
else
{
Snackbar.Add("Fehler beim Speichern", Severity.Error);
}
}
finally
{
_isSaving = false;
}
}
private async Task SetDefaultClub(Guid clubId)
{
_isSaving = true;
try
{
var dto = new UpdateUserProfileDto
{
DefaultClubId = clubId
};
var result = await UserService.UpdateProfileAsync(dto);
if (result != null)
{
_user = result;
Snackbar.Add("Standard-Club geaendert", Severity.Success);
}
else
{
Snackbar.Add("Fehler beim Aendern", Severity.Error);
}
}
finally
{
_isSaving = false;
}
}
}