Add dynamic theme and dark mode functionality

Introduced `ThemeService` to manage themes and dark mode, with a new footer UI for theme selection and toggling dark mode. Added dynamic CSS loading and Bootstrap theme support. Registered `UserPreferencesService` and `ThemeService` for SSR compatibility. Removed hardcoded DevExpress theme links.

Fixed German localization issues in `LoginReceiverPage` and `LoginSenderPage`. Updated `app.css` with styles for the new footer controls. Simplified font loading in `MainLayout.razor`.
This commit is contained in:
2026-09-03 09:24:13 +02:00
parent 5098deedd1
commit 7fc2c14f9c
7 changed files with 148 additions and 32 deletions

View File

@@ -321,6 +321,13 @@ try
// User Preferences (theme, dark mode, grid layouts — stored in TBDD_CACHE)
// Registration handled by Infrastructure DependencyInjection (AddEnvelopeGeneratorInfrastructureServices)
// Client-side services that MainLayout depends on must also be registered here
// so that Blazor Auto prerender (SSR pass) can resolve them on the server.
// ThemeService.InitializeAsync() uses IJSRuntime but is only called in OnAfterRenderAsync
// (never during prerender), so registering it server-side is safe.
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.UserPreferencesService>();
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.ThemeService>();
// Business Services (Server specific)
builder.Services.AddScoped<DocumentService>();
builder.Services.AddScoped<AuthService>();

View File

@@ -365,4 +365,63 @@ article {
.receiver-footer__sep {
opacity: 0.4;
}
/* ── Footer: Theme & Dark Mode Controls ─────────────────────────────────── */
/* Positioned at the right end of the fixed footer bar. */
/* Styled to match the footer's gradient background — small, unobtrusive. */
.sf-footer-controls {
display: flex;
align-items: center;
gap: 6px;
margin-left: auto; /* push to the right */
}
.sf-footer-select {
height: 22px;
padding: 0 5px;
font-size: 0.68rem;
line-height: 1;
color: rgba(255, 255, 255, 0.85);
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.22);
border-radius: 4px;
cursor: pointer;
outline: none;
/* Native select options use browser defaults — acceptable in a footer context */
}
.sf-footer-select:hover,
.sf-footer-select:focus {
background: rgba(255, 255, 255, 0.20);
border-color: rgba(255, 255, 255, 0.38);
}
/* Force option text to be readable in browsers that support it */
.sf-footer-select option {
color: #1e2e3e;
background: #ffffff;
}
.sf-footer-toggle {
width: 24px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
color: rgba(255, 255, 255, 0.85);
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.22);
border-radius: 4px;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
flex-shrink: 0;
}
.sf-footer-toggle:hover {
background: rgba(255, 255, 255, 0.24);
border-color: rgba(255, 255, 255, 0.40);
color: #ffffff;
}