multi-tenant implementierung

This commit is contained in:
MarcWieland
2026-06-24 23:48:04 +02:00
parent d646cfd28d
commit ac06059e2a
41 changed files with 2530 additions and 238 deletions
@@ -3,6 +3,7 @@
@inject NavigationManager Nav
@inject IUserNotificationService UserNotificationService
@inject IJSRuntime JSRuntime
@inject HttpClient Http
@implements IDisposable
@using System.Security.Claims
@@ -12,7 +13,7 @@
<MudSnackbarProvider />
<MudLayout>
<MudAppBar Elevation="0" Style="background: #0F172A; border-bottom: 1px solid rgba(255, 255, 255, 0.08);">
<MudAppBar Elevation="0" Style="background: color-mix(in srgb, var(--mud-palette-appbar-background) 85%, transparent); border-bottom: 1px solid rgba(255, 255, 255, 0.08); color: var(--mud-palette-appbar-text);">
<AuthorizeView>
<Authorized>
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="ToggleDrawer" Class="d-flex d-md-none mr-2" />
@@ -50,9 +51,74 @@
private bool _drawerOpen = true;
private bool _isDarkMode;
protected override void OnInitialized()
protected override async Task OnInitializedAsync()
{
UserNotificationService.OnUserDeleted += HandleUserDeleted;
try
{
var tenantInfo = await Http.GetFromJsonAsync<TenantInfo>("api/tenant/current");
if (tenantInfo?.IsTenant == true && !string.IsNullOrEmpty(tenantInfo.PrimaryColor))
{
var primaryColor = tenantInfo.PrimaryColor;
var secondaryColor = tenantInfo.SecondaryColor ?? tenantInfo.PrimaryColor;
_theme.PaletteLight.Primary = primaryColor;
_theme.PaletteLight.Secondary = secondaryColor;
_theme.PaletteDark.Primary = primaryColor;
_theme.PaletteDark.Secondary = secondaryColor;
// Dynamically apply background colors to navbar
_theme.PaletteLight.AppbarBackground = primaryColor;
_theme.PaletteLight.DrawerBackground = primaryColor;
_theme.PaletteDark.AppbarBackground = primaryColor;
_theme.PaletteDark.DrawerBackground = primaryColor;
// Adjust text/icon contrast based on background luminance
var isLight = IsLightColor(primaryColor);
var textCol = isLight ? "#0F172A" : "#FFFFFF";
var subTextCol = isLight ? "rgba(15, 23, 42, 0.70)" : "rgba(255, 255, 255, 0.70)";
_theme.PaletteLight.AppbarText = textCol;
_theme.PaletteLight.DrawerText = subTextCol;
_theme.PaletteLight.DrawerIcon = subTextCol;
_theme.PaletteLight.PrimaryContrastText = textCol;
_theme.PaletteDark.AppbarText = textCol;
_theme.PaletteDark.DrawerText = subTextCol;
_theme.PaletteDark.DrawerIcon = subTextCol;
_theme.PaletteDark.PrimaryContrastText = textCol;
}
}
catch
{
// Ignore background error
}
}
private bool IsLightColor(string hexColor)
{
if (string.IsNullOrEmpty(hexColor)) return false;
hexColor = hexColor.TrimStart('#');
if (hexColor.Length == 3)
{
hexColor = new string(new[] { hexColor[0], hexColor[0], hexColor[1], hexColor[1], hexColor[2], hexColor[2] });
}
if (hexColor.Length != 6) return false;
try
{
var r = Convert.ToInt32(hexColor.Substring(0, 2), 16);
var g = Convert.ToInt32(hexColor.Substring(2, 2), 16);
var b = Convert.ToInt32(hexColor.Substring(4, 2), 16);
var luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255.0;
return luminance > 0.6; // slightly conservative threshold for white text readability
}
catch
{
return false;
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -14,6 +14,7 @@
}
::deep .modern-drawer {
background-color: var(--mud-palette-drawer-background) !important;
border-right: 1px solid rgba(255, 255, 255, 0.08) !important;
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.1) !important;
transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
@@ -21,7 +22,7 @@
/* Glassmorphism for the appbar */
::deep .mud-appbar {
background: rgba(15, 23, 42, 0.85) !important;
background: color-mix(in srgb, var(--mud-palette-appbar-background) 85%, transparent) !important;
backdrop-filter: blur(12px) !important;
-webkit-backdrop-filter: blur(12px) !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
@@ -1,4 +1,5 @@
@inject IJSRuntime JSRuntime
@inject HttpClient Http
<div class="nav-container onboarding-nav-menu">
@* --- Brand / Logo Section --- *@
<div class="nav-header">
@@ -37,6 +38,11 @@
<span class="admin-divider-text">Admin</span>
<hr class="admin-divider-line" />
</div>
<MudTooltip Text="Firmen verwalten (SaaS)" Placement="Placement.Right">
<MudNavLink Href="superadmin" Icon="@Icons.Material.Filled.Business" Class="custom-nav-link admin-link">
Firmen verwalten
</MudNavLink>
</MudTooltip>
<MudTooltip Text="Benutzerverwaltung" Placement="Placement.Right">
<MudNavLink Href="admin/users" Icon="@Icons.Material.Filled.AdminPanelSettings" Class="custom-nav-link admin-link">
Benutzerverwaltung
@@ -61,7 +67,7 @@
</div>
<div class="user-info">
<span class="user-name">@context.User.Identity?.Name</span>
<span class="user-status">Online</span>
<span class="user-status">@_companyName</span>
</div>
</div>
</NavLink>
@@ -93,6 +99,23 @@
[Parameter] public EventCallback OnToggleDrawer { get; set; }
private bool _showVersionBadge;
private string _companyName = "Hauptdomain";
protected override async Task OnInitializedAsync()
{
try
{
var tenantInfo = await Http.GetFromJsonAsync<TenantInfo>("api/tenant/current");
if (tenantInfo?.IsTenant == true && !string.IsNullOrEmpty(tenantInfo.Name))
{
_companyName = tenantInfo.Name;
}
}
catch
{
// Ignore background error
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
@@ -44,7 +44,7 @@
}
::deep .logo-icon {
color: #0EA5E9 !important; /* Sky 500 */
color: var(--mud-palette-secondary) !important;
font-size: 1.6rem !important;
transition: opacity 0.2s ease;
}
@@ -52,7 +52,7 @@
.brand-text {
font-size: 1.1rem;
font-weight: 800;
color: #FFFFFF;
color: var(--mud-palette-appbar-text);
letter-spacing: 0.5px;
white-space: nowrap;
opacity: 1;
@@ -104,7 +104,7 @@
::deep .custom-nav-link,
::deep .custom-nav-link .mud-nav-link-text,
::deep .custom-nav-link .mud-nav-link-icon-default {
color: #94A3B8 !important; /* Slate 400 */
color: var(--mud-palette-drawer-text) !important;
}
/* NavLink hover */
@@ -115,15 +115,15 @@
::deep .custom-nav-link:hover,
::deep .custom-nav-link:hover .mud-nav-link-text,
::deep .custom-nav-link:hover .mud-nav-link-icon-default {
color: #F8FAFC !important; /* Slate 50 */
color: var(--mud-palette-appbar-text) !important;
}
/* NavLink active state (Fixes active color contrast issue) */
::deep .custom-nav-link.active,
::deep .custom-nav-link .active {
background: linear-gradient(90deg, rgba(14, 165, 233, 0.15) 0%, rgba(14, 165, 233, 0.04) 100%) !important;
background: linear-gradient(90deg, color-mix(in srgb, var(--mud-palette-secondary) 15%, transparent) 0%, color-mix(in srgb, var(--mud-palette-secondary) 4%, transparent) 100%) !important;
font-weight: 600 !important;
box-shadow: inset 3px 0 0 #0EA5E9 !important;
box-shadow: inset 3px 0 0 var(--mud-palette-secondary) !important;
}
::deep .custom-nav-link.active,
@@ -132,7 +132,7 @@
::deep .custom-nav-link .active .mud-nav-link-text,
::deep .custom-nav-link.active .mud-nav-link-icon-default,
::deep .custom-nav-link .active .mud-nav-link-icon-default {
color: #0EA5E9 !important; /* Sky 500 */
color: var(--mud-palette-secondary) !important;
}
/* Admin link color overrides & active state */
@@ -208,7 +208,7 @@
::deep .user-avatar {
width: 36px !important;
height: 36px !important;
background: linear-gradient(135deg, #0EA5E9 0%, #0284C7 100%) !important;
background: linear-gradient(135deg, var(--mud-palette-secondary) 0%, var(--mud-palette-secondary-darken) 100%) !important;
color: #FFFFFF !important;
font-weight: 700 !important;
font-size: 0.875rem !important;
@@ -222,7 +222,7 @@
width: 10px;
height: 10px;
background-color: #10B981; /* Emerald 500 */
border: 2px solid #0F172A;
border: 2px solid var(--mud-palette-drawer-background);
border-radius: 50%;
}
@@ -236,7 +236,7 @@
.user-name {
font-size: 0.85rem;
font-weight: 600;
color: #FFFFFF;
color: var(--mud-palette-appbar-text);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;