235 lines
9.5 KiB
Plaintext
235 lines
9.5 KiB
Plaintext
@page "/login"
|
||
@rendermode InteractiveWebAssembly
|
||
@attribute [AllowAnonymous]
|
||
@inject IAuthService AuthService
|
||
@inject NavigationManager Nav
|
||
@inject IJSRuntime JSRuntime
|
||
|
||
<PageTitle>Anmelden – Timetracker</PageTitle>
|
||
|
||
<MudContainer MaxWidth="MaxWidth.Small" Class="mt-16">
|
||
<MudStack AlignItems="AlignItems.Center" Spacing="4">
|
||
|
||
@* ── Logo / Header ── *@
|
||
<MudStack AlignItems="AlignItems.Center" Spacing="1">
|
||
<MudIcon Icon="@Icons.Material.Filled.AccessTime"
|
||
Style="font-size:4rem; color:#0EA5E9" />
|
||
<MudText Typo="Typo.h4" Style="font-weight:700; color:#0EA5E9">Timetracker</MudText>
|
||
</MudStack>
|
||
|
||
<MudPaper Elevation="4" Class="pa-6 rounded-xl" Style="width:100%">
|
||
@* ── Tab Navigation ── *@
|
||
<MudStack Row="true" Justify="Justify.Center" Class="mb-4">
|
||
<MudButton OnClick="@(() => SetTab(0))"
|
||
Variant="@(_activeTab == 0 ? Variant.Filled : Variant.Text)"
|
||
Color="Color.Primary"
|
||
Style="min-width: 120px; border-radius: 20px;">
|
||
Anmelden
|
||
</MudButton>
|
||
<MudButton OnClick="@(() => SetTab(1))"
|
||
Variant="@(_activeTab == 1 ? Variant.Filled : Variant.Text)"
|
||
Color="Color.Primary"
|
||
Style="min-width: 120px; border-radius: 20px;">
|
||
Registrieren
|
||
</MudButton>
|
||
</MudStack>
|
||
|
||
<MudDivider Class="mb-6" />
|
||
|
||
@if (_activeTab == 0)
|
||
{
|
||
@* ── Login Form ── *@
|
||
<MudStack Spacing="3">
|
||
@if (_error != null)
|
||
{
|
||
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
||
}
|
||
<EditForm Model="@_loginModel" OnValidSubmit="HandleLogin">
|
||
<MudStack Spacing="3">
|
||
<MudTextField T="string"
|
||
Label="Benutzername"
|
||
Variant="Variant.Outlined"
|
||
Adornment="Adornment.Start"
|
||
AdornmentIcon="@Icons.Material.Filled.Person"
|
||
@bind-Value="_loginModel.Username"
|
||
Required="true"
|
||
AutoFocus="true" />
|
||
<MudTextField T="string"
|
||
Label="Passwort"
|
||
Variant="Variant.Outlined"
|
||
Adornment="Adornment.Start"
|
||
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||
InputType="InputType.Password"
|
||
@bind-Value="_loginModel.Password"
|
||
Required="true" />
|
||
<MudButton ButtonType="ButtonType.Submit"
|
||
Variant="Variant.Filled"
|
||
Color="Color.Primary"
|
||
FullWidth="true"
|
||
Size="Size.Large"
|
||
StartIcon="@Icons.Material.Filled.Login"
|
||
Class="mt-2"
|
||
Disabled="_loading">
|
||
@if (_loading)
|
||
{
|
||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||
}
|
||
Anmelden
|
||
</MudButton>
|
||
</MudStack>
|
||
</EditForm>
|
||
</MudStack>
|
||
}
|
||
else
|
||
{
|
||
@* ── Register Form ── *@
|
||
<MudStack Spacing="3">
|
||
@if (_error != null)
|
||
{
|
||
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
||
}
|
||
<EditForm Model="@_registerModel" OnValidSubmit="HandleRegister">
|
||
<MudStack Spacing="3">
|
||
<MudTextField T="string"
|
||
Label="Benutzername"
|
||
Variant="Variant.Outlined"
|
||
Adornment="Adornment.Start"
|
||
AdornmentIcon="@Icons.Material.Filled.Person"
|
||
@bind-Value="_registerModel.Username"
|
||
Required="true"
|
||
HelperText="Mindestens 3 Zeichen" />
|
||
<MudTextField T="string"
|
||
Label="Passwort"
|
||
Variant="Variant.Outlined"
|
||
Adornment="Adornment.Start"
|
||
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||
InputType="InputType.Password"
|
||
@bind-Value="_registerModel.Password"
|
||
Required="true"
|
||
HelperText="Mindestens 6 Zeichen" />
|
||
<input type="text" style="display: none;" tabindex="-1" autocomplete="off" @bind="_honeypot" />
|
||
|
||
<MudCheckBox @bind-Value="_acceptAgb" Color="Color.Secondary" Class="mt-1">
|
||
<MudText Typo="Typo.body2">Ich akzeptiere die <a href="/agb" target="_blank" style="color: var(--mud-palette-secondary); text-decoration: underline; font-weight: 600;">AGB</a>.</MudText>
|
||
</MudCheckBox>
|
||
|
||
<MudButton ButtonType="ButtonType.Submit"
|
||
Variant="Variant.Filled"
|
||
Color="Color.Secondary"
|
||
FullWidth="true"
|
||
Size="Size.Large"
|
||
StartIcon="@Icons.Material.Filled.PersonAdd"
|
||
Class="mt-2"
|
||
Disabled="_loading || !_acceptAgb">
|
||
@if (_loading)
|
||
{
|
||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||
}
|
||
Konto erstellen
|
||
</MudButton>
|
||
</MudStack>
|
||
</EditForm>
|
||
</MudStack>
|
||
}
|
||
</MudPaper>
|
||
</MudStack>
|
||
</MudContainer>
|
||
|
||
@code {
|
||
private int _activeTab = 0;
|
||
private string? _error;
|
||
private bool _loading;
|
||
private string _honeypot = "";
|
||
private bool _acceptAgb;
|
||
|
||
private readonly AuthModel _loginModel = new();
|
||
private readonly AuthModel _registerModel = new();
|
||
|
||
[SupplyParameterFromQuery(Name = "error")]
|
||
public string? ErrorParam { get; set; }
|
||
|
||
[SupplyParameterFromQuery(Name = "tab")]
|
||
public string? TabParam { get; set; }
|
||
|
||
protected override void OnParametersSet()
|
||
{
|
||
_error = ErrorParam switch
|
||
{
|
||
"invalid" => "Benutzername oder Passwort falsch.",
|
||
not null => Uri.UnescapeDataString(ErrorParam),
|
||
_ => null
|
||
};
|
||
_activeTab = TabParam == "register" ? 1 : 0;
|
||
}
|
||
|
||
private void SetTab(int tab)
|
||
{
|
||
_activeTab = tab;
|
||
_error = null;
|
||
}
|
||
|
||
private async Task HandleLogin()
|
||
{
|
||
_loading = true;
|
||
_error = null;
|
||
try
|
||
{
|
||
var user = await AuthService.LoginAsync(_loginModel.Username, _loginModel.Password);
|
||
if (user != null)
|
||
{
|
||
Nav.NavigateTo("/", forceLoad: true); // forceLoad forces state update/re-render of the root app
|
||
}
|
||
else
|
||
{
|
||
_error = "Benutzername oder Passwort falsch.";
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_error = $"Login Fehler: {ex.Message}";
|
||
}
|
||
finally
|
||
{
|
||
_loading = false;
|
||
}
|
||
}
|
||
|
||
private async Task HandleRegister()
|
||
{
|
||
if (!_acceptAgb)
|
||
{
|
||
_error = "Du musst die AGB akzeptieren.";
|
||
return;
|
||
}
|
||
_loading = true;
|
||
_error = null;
|
||
try
|
||
{
|
||
var (user, error) = await AuthService.RegisterAsync(_registerModel.Username, _registerModel.Password, _honeypot);
|
||
if (user != null)
|
||
{
|
||
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "showOnboarding", "true");
|
||
Nav.NavigateTo("/", forceLoad: true);
|
||
}
|
||
else
|
||
{
|
||
_error = error ?? "Registrierung fehlgeschlagen.";
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_error = $"Registrierungs Fehler: {ex.Message}";
|
||
}
|
||
finally
|
||
{
|
||
_loading = false;
|
||
}
|
||
}
|
||
|
||
private class AuthModel
|
||
{
|
||
public string Username { get; set; } = "";
|
||
public string Password { get; set; } = "";
|
||
}
|
||
}
|