- Introduced AuthService, IAuthApiClient, and AuthApiClient for managing authentication state and API calls (login, logout, session restore). - Added Login.razor and LoginLayout.razor for the login page, including styling and logic. - MainLayout.razor now checks authentication on load, restores sessions from sessionStorage, and redirects to /login if unauthenticated. Displays username and logout button when logged in. - Implemented JS interop (authStorage) for persisting authentication info in sessionStorage. - Registered AuthService, CookieContainer, and API clients in Program.cs to share cookies and support authentication. - Updated AppSettings and appsettings files to support separate ApiBaseUrl and DataApiBaseUrl. - Minor CSS improvements for username display in the top bar.
165 lines
4.7 KiB
Plaintext
165 lines
4.7 KiB
Plaintext
@inherits LayoutComponentBase
|
||
@implements IDisposable
|
||
@inject ThemeState ThemeState
|
||
@inject IJSRuntime JS
|
||
@inject AuthService AuthService
|
||
@inject IAuthApiClient AuthApiClient
|
||
@inject NavigationManager Navigation
|
||
|
||
<div class="page @(ThemeState.IsDarkMode ? "app-dark" : "app-light") @(ThemeState.IsNativeDarkTheme ? "native-dark" : "")">
|
||
<div class="sidebar">
|
||
<NavMenu />
|
||
</div>
|
||
|
||
<main>
|
||
<div class="top-row px-4">
|
||
<DxComboBox Data="@ThemeState.AvailableThemes"
|
||
Value="@ThemeState.CurrentThemeName"
|
||
ValueChanged="@((string t) => ThemeState.SetTheme(t))"
|
||
style="width: 130px;" />
|
||
<span style="margin-left: 12px;">
|
||
<DxButton Text="@(ThemeState.IsDarkMode ? "Dark Mode aus" : "Dark Mode an")"
|
||
Click="ToggleTheme" />
|
||
</span>
|
||
@if (AuthService.IsAuthenticated)
|
||
{
|
||
<span class="ms-auto d-flex align-items-center gap-2">
|
||
<span class="top-row-username">@AuthService.UserName</span>
|
||
<DxButton Text="Abmelden"
|
||
Click="LogoutAsync"
|
||
RenderStyle="ButtonRenderStyle.Secondary" />
|
||
</span>
|
||
}
|
||
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
|
||
</div>
|
||
|
||
<article class="content px-4">
|
||
@if (_checkingAuth)
|
||
{
|
||
<div class="loading-container">
|
||
<span>Wird geladen…</span>
|
||
</div>
|
||
}
|
||
else
|
||
{
|
||
@Body
|
||
}
|
||
</article>
|
||
</main>
|
||
</div>
|
||
|
||
<div id="blazor-error-ui">
|
||
An unhandled error has occurred.
|
||
<a href="" class="reload">Reload</a>
|
||
<a class="dismiss">🗙</a>
|
||
</div>
|
||
|
||
@code {
|
||
private bool _isInteractive;
|
||
private bool _checkingAuth = true;
|
||
|
||
protected override void OnInitialized()
|
||
{
|
||
ThemeState.OnChange += OnThemeChanged;
|
||
AuthService.OnChange += OnAuthChanged;
|
||
}
|
||
|
||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||
{
|
||
if (firstRender)
|
||
{
|
||
_isInteractive = true;
|
||
|
||
if (!AuthService.IsAuthenticated)
|
||
{
|
||
try
|
||
{
|
||
var username = await JS.InvokeAsync<string?>("authStorage.get", "auth_user");
|
||
var cookie = await JS.InvokeAsync<string?>("authStorage.get", "auth_cookie");
|
||
|
||
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(cookie))
|
||
{
|
||
var restored = await AuthApiClient.RestoreAsync(username, cookie);
|
||
if (!restored)
|
||
{
|
||
await JS.InvokeVoidAsync("authStorage.clear");
|
||
Navigation.NavigateTo("/login", replace: true);
|
||
return;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Navigation.NavigateTo("/login", replace: true);
|
||
return;
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
Navigation.NavigateTo("/login", replace: true);
|
||
return;
|
||
}
|
||
}
|
||
|
||
_checkingAuth = false;
|
||
StateHasChanged();
|
||
}
|
||
|
||
await ApplyDxDarkOverrideAsync();
|
||
}
|
||
|
||
private async Task LogoutAsync()
|
||
{
|
||
try
|
||
{
|
||
await AuthApiClient.LogoutAsync();
|
||
await JS.InvokeVoidAsync("authStorage.clear");
|
||
}
|
||
catch
|
||
{
|
||
// Fehler beim Abmelden ignorieren, trotzdem weiterleiten
|
||
}
|
||
|
||
Navigation.NavigateTo("/login", replace: true);
|
||
}
|
||
|
||
private void OnAuthChanged()
|
||
{
|
||
InvokeAsync(StateHasChanged);
|
||
}
|
||
|
||
private void OnThemeChanged()
|
||
{
|
||
InvokeAsync(async () =>
|
||
{
|
||
StateHasChanged();
|
||
if (_isInteractive)
|
||
await ApplyDxDarkOverrideAsync();
|
||
});
|
||
}
|
||
|
||
private async Task ApplyDxDarkOverrideAsync()
|
||
{
|
||
if (!_isInteractive) return;
|
||
try
|
||
{
|
||
bool needsOverride = ThemeState.IsDarkMode && !ThemeState.IsNativeDarkTheme;
|
||
await JS.InvokeVoidAsync("setDxDarkOverride", needsOverride);
|
||
}
|
||
catch (JSException)
|
||
{
|
||
// JS-Funktion noch nicht verfügbar – kein Circuit-Crash
|
||
}
|
||
}
|
||
|
||
private void ToggleTheme()
|
||
{
|
||
ThemeState.SetDarkMode(!ThemeState.IsDarkMode);
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
ThemeState.OnChange -= OnThemeChanged;
|
||
AuthService.OnChange -= OnAuthChanged;
|
||
}
|
||
}
|