@using System.Security.Claims @using Alica_Technol...

تم الإنشاء في: ١٤ سبتمبر ٢٠٢٥

تمت الإجابة باستخدام GPT-5 Thinking بواسطة Chat01

سؤال

@using System.Security.Claims
@using Alica_Technologies_LLP.Models

@functions {
public string GetUserInitials(string userName)
{
if (string.IsNullOrEmpty(userName))
return "U";

text
var parts = userName.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 0) return "U"; if (parts.Length == 1) return parts[0].Substring(0, Math.Min(2, parts[0].Length)).ToUpper(); return string.Concat(parts.Select(p => p.FirstOrDefault())).ToUpper(); } public string GetUserRole() { return User.FindFirstValue(ClaimTypes.Role) ?? "User"; } public bool HasRole(string roleName) { return User.IsInRole(roleName); } public bool HasAnyRole(params string[] roleNames) { return roleNames.Any(role => User.IsInRole(role)); }

}

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - Alica Technologies LLP</title> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> </head> <body> <!-- Top Navigation Bar --> <nav class="navbar"> <div class="navbar-container"> <!-- Logo/Brand --> <a class="navbar-brand" href="@Url.Action("Index", "Home")"> Alica Technologies LLP </a>
text
<!-- Main Navigation --> <div class="navbar-nav"> @if (User.Identity.IsAuthenticated) { <!-- User Profile Dropdown --> <div class="user-dropdown"> <button class="user-profile-btn" type="button"> <div class="user-avatar"> <span class="user-initials">@GetUserInitials(User.Identity?.Name ?? "")</span> </div> <span class="username-text">@(User.Identity?.Name ?? "User")</span> <svg class="dropdown-arrow" width="12" height="12" viewBox="0 0 12 12" fill="none"> <path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> <!-- Dropdown Menu --> <div id="userDropdown" class="user-dropdown-menu"> <div class="dropdown-header"> <div class="dropdown-user-info"> <div class="dropdown-avatar"> <span class="dropdown-user-initials">@GetUserInitials(User.Identity?.Name ?? "")</span> </div> <div> <div class="dropdown-username">@(User.Identity?.Name ?? "User")</div> <div class="dropdown-user-role">@GetUserRole()</div> </div> </div> </div> <div class="dropdown-divider"></div> <!-- Profile Link --> <a href="@Url.Action("MyProfile", "Account")" class="dropdown-item"> <svg class="dropdown-item-icon" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M8 8C9.65685 8 11 6.65685 11 5C11 3.34315 9.65685 2 8 2C6.34315 2 5 3.34315 5 5C5 6.65685 6.34315 8 8 8Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> <path d="M13 14C13 11.7909 10.7614 10 8 10C5.23858 10 3 11.7909 3 14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> My Profile </a> <!-- Role-based menu items --> @if (HasRole("Administrator")) { <a href="@Url.Action("Index", "Dashboard")" class="dropdown-item"> <svg class="dropdown-item-icon" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M2 6H14M2 10H14M4 14H12C13.1046 14 14 13.1046 14 12V4C14 2.89543 13.1046 2 12 2H4C2.89543 2 2 2.89543 2 4V12C2 13.1046 2.89543 14 4 14Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> Dashboard </a> <div class="dropdown-submenu"> <button class="dropdown-item dropdown-submenu-btn" type="button" onclick="toggleUserManagementSubmenu(event)"> <svg class="dropdown-item-icon" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M8 2V14M2 8H14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5"/> </svg> User Management <svg class="dropdown-submenu-arrow" width="12" height="12" viewBox="0 0 12 12" fill="none"> <path d="M3.5 4.5L6 7L8.5 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> <div class="dropdown-submenu-content" id="userManagementSubmenu"> <a href="@Url.Action("Index", "UserManagement")" class="dropdown-submenu-item"> <svg class="dropdown-item-icon" width="14" height="14" viewBox="0 0 14 14" fill="none"> <path d="M2 5.5H12M2 8.5H8M3 11H11C11.5304 11 12.0391 10.7893 12.4142 10.4142C12.7893 10.0391 13 9.53043 13 9V3C13 2.46957 12.7893 1.96086 12.4142 1.58579C12.0391 1.21071 11.5304 1 11 1H3C2.46957 1 1.96086 1.21071 1.58579 1.58579C1.21071 1.96086 1 2.46957 1 3V9C1 9.53043 1.21071 10.0391 1.58579 10.4142C1.96086 10.7893 2.46957 11 3 11Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> Manage Users </a> <a href="@Url.Action("CreateUser", "UserManagement")" class="dropdown-submenu-item"> <svg class="dropdown-item-icon" width="14" height="14" viewBox="0 0 14 14" fill="none"> <path d="M7 1V13M1 7H13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> <circle cx="7" cy="7" r="6.5" stroke="currentColor" stroke-width="1.5"/> </svg> Create User </a> </div> </div> } @if (HasAnyRole("Manager", "Administrator")) { <a href="#" class="dropdown-item"> <svg class="dropdown-item-icon" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M9 14H7C4.23858 14 2 11.7614 2 9V7C2 4.23858 4.23858 2 7 2H9C11.7614 2 14 4.23858 14 7V9C14 11.7614 11.7614 14 9 14Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> <path d="M5 6H11M5 10H9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> Reports </a> } <div class="dropdown-divider"></div> <!-- Logout --> <form asp-action="Logout" asp-controller="Account" method="post" style="display: contents;"> <button type="submit" class="dropdown-item dropdown-item-logout"> <svg class="dropdown-item-icon" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M6 14H4C2.89543 14 2 13.1046 2 12V4C2 2.89543 2.89543 2 4 2H6M10 11L14 8M14 8L10 5M14 8H6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> Logout </button> </form> </div> </div> } else { <a class="nav-link" href="@Url.Action("Login", "Account")">Login</a> <a class="nav-link" href="@Url.Action("Register", "Account")">Register</a> } </div> </div> </nav> <!-- Sidebar for Role-based Navigation (Hidden by default, can be enabled later) --> <aside class="sidebar" id="sidebar"> <div class="sidebar-header"> <h3 class="sidebar-title">Navigation</h3> <button class="sidebar-close" onclick="toggleSidebar()"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none"> <path d="M5 5L15 15M15 5L5 15" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> </svg> </button> </div> <nav class="sidebar-nav"> <!-- Role-based sidebar items --> @if (User.Identity.IsAuthenticated) { @if (HasRole("Administrator")) { <div class="sidebar-section"> <h4 class="sidebar-section-title">Administration</h4> <a href="#" class="sidebar-item">User Management</a> <a href="#" class="sidebar-item">System Settings</a> <a href="#" class="sidebar-item">Audit Logs</a> </div> } @if (HasAnyRole("Manager", "Administrator")) { <div class="sidebar-section"> <h4 class="sidebar-section-title">Management</h4> <a href="#" class="sidebar-item">Reports</a> <a href="#" class="sidebar-item">Analytics</a> <a href="#" class="sidebar-item">Team Overview</a> </div> } @if (HasAnyRole("Operations", "Administrator")) { <div class="sidebar-section"> <h4 class="sidebar-section-title">Operations</h4> <a href="#" class="sidebar-item">Process Management</a> <a href="#" class="sidebar-item">Workflow Status</a> <a href="#" class="sidebar-item">Task Queue</a> </div> } @if (HasAnyRole("Accounts", "Administrator")) { <div class="sidebar-section"> <h4 class="sidebar-section-title">Accounts</h4> <a href="#" class="sidebar-item">Financial Reports</a> <a href="#" class="sidebar-item">Invoice Management</a> <a href="#" class="sidebar-item">Tax Compliance</a> </div> } @if (HasAnyRole("Store", "Administrator")) { <div class="sidebar-section"> <h4 class="sidebar-section-title">Store</h4> <a href="#" class="sidebar-item">Inventory</a> <a href="#" class="sidebar-item">Purchase Orders</a> <a href="#" class="sidebar-item">Supplier Management</a> </div> } } </nav> </aside> <!-- Main Content Area --> <main class="flex-grow-1"> <div class="container py-4"> @RenderBody() </div> </main> <!-- Footer --> <footer class="border-t mt-auto py-6 bg-white"> <div class="container"> <div class="text-center"> <p class="mb-0 text-gray-500 font-size-sm"> &copy; 2024 Alica Technologies LLP. All rights reserved. </p> </div> </div> </footer> <!-- JavaScript for dropdown and sidebar functionality --> <script> // Initialize dropdown functionality document.addEventListener('DOMContentLoaded', function() { initializeDropdown(); }); function initializeDropdown() { const userProfileBtn = document.querySelector('.user-profile-btn'); const userDropdown = document.getElementById('userDropdown'); if (!userProfileBtn || !userDropdown) { console.warn('Dropdown elements not found'); return; } console.log('Dropdown initialized successfully'); userProfileBtn.addEventListener('click', function(event) { event.preventDefault(); event.stopPropagation(); console.log('Dropdown button clicked'); console.log('Before toggle - has show class:', userDropdown.classList.contains('show')); console.log('Dropdown element:', userDropdown); // Toggle dropdown const isOpen = userDropdown.classList.contains('show'); if (isOpen) { userDropdown.classList.remove('show'); userDropdown.style.display = 'none'; console.log('Dropdown closed'); } else { // Close any other open dropdowns first document.querySelectorAll('.user-dropdown.show').forEach(dropdown => { dropdown.classList.remove('show'); dropdown.style.display = 'none'; }); userDropdown.classList.add('show'); // Force inline style as backup userDropdown.style.display = 'block'; userDropdown.style.opacity = '1'; userDropdown.style.visibility = 'visible'; userDropdown.style.transform = 'translateY(0)'; userDropdown.style.pointerEvents = 'auto'; console.log('Dropdown opened'); console.log('After adding - has show class:', userDropdown.classList.contains('show')); console.log('Inline styles applied:', userDropdown.style.cssText); // Force a style recalculation userDropdown.offsetHeight; } // Setup outside click listener setupOutsideClickListener(); // Debug: Log the computed style setTimeout(() => { const computedStyle = window.getComputedStyle(userDropdown); console.log('Computed display:', computedStyle.display); console.log('Computed visibility:', computedStyle.visibility); console.log('Computed opacity:', computedStyle.opacity); console.log('Current classes:', userDropdown.className); }, 100); }); // Prevent dropdown from closing when clicking inside it userDropdown.addEventListener('click', function(event) { event.stopPropagation(); }); } function setupOutsideClickListener() { // Remove existing listener to prevent duplicates document.removeEventListener('click', closeDropdownOutside); // Add new listener with small delay setTimeout(() => { document.addEventListener('click', closeDropdownOutside); }, 100); } function closeDropdownOutside(event) { const userProfileBtn = document.querySelector('.user-profile-btn'); const userDropdown = document.getElementById('userDropdown'); if (userProfileBtn && userDropdown && !userProfileBtn.contains(event.target) && !userDropdown.contains(event.target)) { userDropdown.classList.remove('show'); userDropdown.style.display = 'none'; userDropdown.style.opacity = ''; userDropdown.style.visibility = ''; userDropdown.style.transform = ''; userDropdown.style.pointerEvents = ''; document.removeEventListener('click', closeDropdownOutside); console.log('Dropdown closed by outside click'); } } // Toggle User Management submenu function toggleUserManagementSubmenu(event) { event.preventDefault(); event.stopPropagation(); const submenu = document.getElementById('userManagementSubmenu'); const arrow = event.currentTarget.querySelector('.dropdown-submenu-arrow'); if (submenu && arrow) { const isOpen = submenu.classList.contains('show'); if (isOpen) { submenu.classList.remove('show'); arrow.style.transform = 'rotate(0deg)'; } else { submenu.classList.add('show'); arrow.style.transform = 'rotate(180deg)'; } } } // Also handle keyboard accessibility document.addEventListener('keydown', function(event) { if (event.key === 'Escape') { const userDropdown = document.getElementById('userDropdown'); if (userDropdown && userDropdown.classList.contains('show')) { userDropdown.classList.remove('show'); userDropdown.style.display = 'none'; userDropdown.style.opacity = ''; userDropdown.style.visibility = ''; userDropdown.style.transform = ''; userDropdown.style.pointerEvents = ''; document.removeEventListener('click', closeDropdownOutside); console.log('Dropdown closed by Escape key'); } } }); </script> @RenderSection("Scripts", required: false)
</body> </html>

Create menu items in the layout such that, it would appear based on the roles. In desktop mode, it should be visible as is. In the mobile view, it would collapse with the buttons allowing us to expand the menu. Make sure you think and reason about its positioning extremely precisely before forming a plan.

/* Swiss Design System - Monochrome & Minimal */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Geist:wght@100;200;300;400;500;600;700;800;900&display=swap');

:root {
/* Swiss Monochrome Palette */
--color-black: #000000;
--color-gray-900: #0a0a0a;
--color-gray-800: #1a1a1a;
--color-gray-700: #2a2a2a;
--color-gray-600: #3a3a3a;
--color-gray-500: #4a4a4a;
--color-gray-400: #6a6a6a;
--color-gray-300: #8a8a8a;
--color-gray-200: #aaaaaa;
--color-gray-100: #cacaca;
--color-gray-50: #eaeaea;
--color-white: #ffffff;
--color-off-white: #fafafa;

text
/* Swiss Typography - Geist as primary, Inter as fallback */ --font-primary: 'Geist', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; --font-mono: 'Geist Mono', 'JetBrains Mono', 'Menlo', 'Monaco', 'Courier New', monospace; /* Swiss Typography Scale */ --font-size-xs: 0.75rem; --font-size-sm: 0.875rem; --font-size-base: 1rem; --font-size-lg: 1.125rem; --font-size-xl: 1.25rem; --font-size-2xl: 1.5rem; --font-size-3xl: 1.875rem; --font-size-4xl: 2.25rem; --font-size-5xl: 3rem; --font-size-6xl: 3.75rem; /* Swiss 8-Point Grid System */ --spacing-px: 1px; --spacing-1: 0.125rem; --spacing-2: 0.25rem; --spacing-3: 0.375rem; --spacing-4: 0.5rem; --spacing-5: 0.625rem; --spacing-6: 0.75rem; --spacing-8: 1rem; --spacing-10: 1.25rem; --spacing-12: 1.5rem; --spacing-16: 2rem; --spacing-20: 2.5rem; --spacing-24: 3rem; --spacing-32: 4rem; --spacing-40: 5rem; --spacing-48: 6rem; --spacing-64: 8rem; /* Swiss Minimal Borders */ --border-sm: 1px solid var(--color-gray-200); --border-md: 1px solid var(--color-gray-300); --border-lg: 2px solid var(--color-gray-400); /* Swiss Minimal Shadows */ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1); --shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.15); --shadow-xl: 0 8px 16px rgba(0, 0, 0, 0.2); /* Swiss Transitions */ --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); --transition-normal: 300ms cubic-bezier(0.4, 0, 0.2, 1); --transition-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1); /* Swiss Layout */ --max-width-sm: 640px; --max-width-md: 768px; --max-width-lg: 1024px; --max-width-xl: 1280px; --max-width-2xl: 1536px; --container-padding: var(--spacing-8);

}

  • {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    }

html {
scroll-behavior: smooth;
}

body {
font-family: var(--font-primary);
font-size: var(--font-size-base);
line-height: 1.5;
color: var(--color-black);
background: var(--color-white);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
font-weight: 400;
}

/* Swiss Typography */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-primary);
font-weight: 600;
line-height: 1.2;
margin-bottom: var(--spacing-8);
color: var(--color-black);
letter-spacing: -0.02em;
}

h1 {
font-size: var(--font-size-5xl);
font-weight: 700;
letter-spacing: -0.04em;
margin-bottom: var(--spacing-12);
}

h2 {
font-size: var(--font-size-4xl);
font-weight: 600;
letter-spacing: -0.03em;
margin-bottom: var(--spacing-10);
}

h3 {
font-size: var(--font-size-3xl);
font-weight: 600;
letter-spacing: -0.02em;
margin-bottom: var(--spacing-8);
}

h4 {
font-size: var(--font-size-2xl);
font-weight: 600;
margin-bottom: var(--spacing-6);
}

h5 {
font-size: var(--font-size-xl);
font-weight: 500;
margin-bottom: var(--spacing-4);
}

h6 {
font-size: var(--font-size-lg);
font-weight: 500;
margin-bottom: var(--spacing-4);
}

p {
margin-bottom: var(--spacing-6);
color: var(--color-gray-700);
line-height: 1.6;
}

a {
color: var(--color-black);
text-decoration: none;
transition: var(--transition-fast);
font-weight: 500;
position: relative;
}

a:hover {
color: var(--color-gray-700);
}

a::after {
content: '';
position: absolute;
width: 0;
height: 1px;
bottom: -2px;
left: 0;
background: var(--color-black);
transition: var(--transition-normal);
}

a:hover::after {
width: 100%;
}

/* Swiss Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--spacing-4) var(--spacing-8);
border: var(--border-sm);
border-radius: 0;
font-family: var(--font-primary);
font-size: var(--font-size-base);
font-weight: 500;
text-decoration: none;
cursor: pointer;
transition: var(--transition-fast);
position: relative;
overflow: hidden;
white-space: nowrap;
background: var(--color-white);
color: var(--color-black);
letter-spacing: -0.01em;
}

.btn:hover {
background: var(--color-gray-50);
border-color: var(--color-gray-300);
}

.btn:active {
background: var(--color-gray-100);
transform: translateY(1px);
}

.btn-primary {
background: var(--color-black);
color: var(--color-white);
border-color: var(--color-black);
}

.btn-primary:hover {
background: var(--color-gray-900);
border-color: var(--color-gray-900);
}

.btn-outline {
background: transparent;
border: var(--border-sm);
color: var(--color-black);
}

.btn-outline:hover {
background: var(--color-black);
color: var(--color-white);
}

.btn-block {
display: flex;
width: 100%;
}

.btn-lg {
padding: var(--spacing-6) var(--spacing-12);
font-size: var(--font-size-lg);
}

.btn-sm {
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-sm);
}

/* Swiss Forms */
.form-group {
margin-bottom: var(--spacing-8);
position: relative;
}

.form-label {
display: block;
font-family: var(--font-primary);
font-weight: 500;
margin-bottom: var(--spacing-2);
color: var(--color-black);
font-size: var(--font-size-sm);
letter-spacing: -0.01em;
}

.form-control {
width: 100%;
padding: var(--spacing-4) var(--spacing-4);
border: var(--border-sm);
border-radius: 0;
font-family: var(--font-primary);
font-size: var(--font-size-base);
transition: var(--transition-fast);
background: var(--color-white);
color: var(--color-black);
box-shadow: var(--shadow-sm);
}

.form-control:focus {
outline: none;
border-color: var(--color-gray-600);
box-shadow: var(--shadow-md);
background: var(--color-off-white);
}

.form-control::placeholder {
color: var(--color-gray-400);
}

.form-select {
width: 100%;
padding: var(--spacing-4) var(--spacing-4);
border: var(--border-sm);
border-radius: 0;
font-family: var(--font-primary);
font-size: var(--font-size-base);
transition: var(--transition-fast);
background: var(--color-white);
color: var(--color-black);
box-shadow: var(--shadow-sm);
cursor: pointer;
}

.form-select:focus {
outline: none;
border-color: var(--color-gray-600);
box-shadow: var(--shadow-md);
}

.form-check {
display: flex;
align-items: center;
margin-bottom: var(--spacing-4);
}

.form-check-input {
margin-right: var(--spacing-2);
cursor: pointer;
width: 16px;
height: 16px;
border: var(--border-sm);
border-radius: 2px;
}

/* Swiss Cards */
.card {
background: var(--color-white);
border: var(--border-sm);
border-radius: 0;
box-shadow: var(--shadow-sm);
overflow: hidden;
transition: var(--transition-fast);
position: relative;
}

.card:hover {
box-shadow: var(--shadow-md);
}

.card-body {
padding: var(--spacing-12);
position: relative;
z-index: 1;
}

.card-header {
padding: var(--spacing-8) var(--spacing-12);
border-bottom: var(--border-sm);
background: var(--color-off-white);
position: relative;
z-index: 1;
}

.card-title {
margin: 0;
font-size: var(--font-size-xl);
font-weight: 600;
color: var(--color-black);
letter-spacing: -0.02em;
}

/* Swiss Navigation */
.navbar {
background: var(--color-white);
border-bottom: var(--border-sm);
box-shadow: var(--shadow-sm);
position: sticky;
top: 0;
z-index: 1000;
padding: var(--spacing-4) 0;
}

.navbar-container {
max-width: var(--max-width-xl);
margin: 0 auto;
padding: 0 var(--container-padding);
display: flex;
justify-content: space-between;
align-items: center;
height: 64px;
}

.navbar-brand {
font-family: var(--font-primary);
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--color-black);
text-decoration: none;
transition: var(--transition-fast);
letter-spacing: -0.02em;
}

.navbar-brand:hover {
color: var(--color-gray-700);
}

.navbar-nav {
display: flex;
align-items: center;
gap: var(--spacing-6);
}

/* User Dropdown Styles */
.user-dropdown {
position: relative;
}

.user-profile-btn {
display: flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-2) var(--spacing-4);
border: var(--border-sm);
background-color: var(--color-white);
color: var(--color-black);
border-radius: 0;
cursor: pointer;
transition: all 0.2s ease;
font-family: var(--font-primary);
font-size: var(--font-size-sm);
font-weight: 500;
}

.user-profile-btn:hover {
background-color: var(--color-gray-50);
border-color: var(--color-gray-800);
cursor: pointer;
}

.user-profile-btn:focus {
outline: 2px solid var(--color-gray-300);
outline-offset: -2px;
}

.user-avatar {
width: 32px;
height: 32px;
border: var(--border-sm);
background-color: var(--color-gray-100);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: var(--font-size-xs);
color: var(--color-gray-700);
}

.user-initials {
text-transform: uppercase;
line-height: 1;
}

.username-text {
font-weight: 500;
color: var(--color-black);
}

.dropdown-arrow {
transition: transform 0.2s ease;
}

.user-dropdown.show .dropdown-arrow {
transform: rotate(180deg);
}

.user-dropdown-menu {
position: absolute;
top: calc(100% + var(--spacing-2));
right: 0;
min-width: 280px;
border: var(--border-sm);
background-color: var(--color-white);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 9999;
display: none;
transform: translateY(-var(--spacing-2));
transition: all 0.2s ease;
}

.user-dropdown.show .user-dropdown-menu {
display: block;
transform: translateY(0);
opacity: 1;
visibility: visible;
}

/* Debug: Add a visual indicator when dropdown is shown */
.user-dropdown.show .user-profile-btn {
border-color: var(--color-gray-800);
background-color: var(--color-gray-50);
}

.dropdown-header {
padding: var(--spacing-6);
border-bottom: var(--border-sm);
}

.dropdown-user-info {
display: flex;
align-items: center;
gap: var(--spacing-4);
}

.dropdown-avatar {
width: 48px;
height: 48px;
border: var(--border-sm);
background-color: var(--color-gray-100);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: var(--font-size-sm);
color: var(--color-gray-700);
}

.dropdown-user-initials {
text-transform: uppercase;
line-height: 1;
}

.dropdown-username {
font-weight: 600;
font-size: var(--font-size-base);
color: var(--color-black);
margin-bottom: var(--spacing-1);
}

.dropdown-user-role {
font-size: var(--font-size-xs);
color: var(--color-gray-500);
text-transform: capitalize;
}

.dropdown-divider {
height: 1px;
background-color: var(--color-gray-200);
margin: var(--spacing-4) 0;
}

.dropdown-item {
display: flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-3) var(--spacing-6);
color: var(--color-black);
text-decoration: none;
font-size: var(--font-size-sm);
font-weight: 400;
transition: all 0.2s ease;
border: none;
background: none;
width: 100%;
text-align: left;
cursor: pointer;
}

.dropdown-item:hover {
background-color: var(--color-gray-50);
color: var(--color-black);
}

.dropdown-item-icon {
width: 16px;
height: 16px;
color: var(--color-gray-500);
flex-shrink: 0;
}

.dropdown-item:hover .dropdown-item-icon {
color: var(--color-gray-700);
}

.dropdown-item-logout {
color: var(--color-gray-700);
}

.dropdown-item-logout:hover {
background-color: var(--color-gray-100);
color: var(--color-black);
}

/* Dropdown Submenu Styles */
.dropdown-submenu {
position: relative;
}

.dropdown-submenu-btn {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding-right: var(--spacing-8);
}

.dropdown-submenu-arrow {
transition: transform 0.2s ease;
margin-left: auto;
}

.dropdown-submenu-content {
position: absolute;
left: 100%;
top: 0;
min-width: 200px;
border: var(--border-sm);
background-color: var(--color-white);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 10000;
display: none;
transform: translateX(var(--spacing-2));
transition: all 0.2s ease;
}

.dropdown-submenu-content.show {
display: block;
transform: translateX(0);
opacity: 1;
visibility: visible;
}

.dropdown-submenu-item {
display: flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-3) var(--spacing-6);
color: var(--color-black);
text-decoration: none;
font-size: var(--font-size-sm);
font-weight: 400;
transition: all 0.2s ease;
border: none;
background: none;
width: 100%;
text-align: left;
cursor: pointer;
}

.dropdown-submenu-item:hover {
background-color: var(--color-gray-50);
color: var(--color-black);
}

.dropdown-submenu-item .dropdown-item-icon {
width: 14px;
height: 14px;
}

/* Sidebar Styles */
.sidebar {
position: fixed;
left: 0;
top: 0;
height: 100vh;
width: 280px;
border-right: var(--border-sm);
background-color: var(--color-white);
z-index: 999;
transform: translateX(-100%);
transition: transform 0.3s ease;
overflow-y: auto;
}

.sidebar.show {
transform: translateX(0);
}

.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-6);
border-bottom: var(--border-sm);
}

.sidebar-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--color-black);
margin: 0;
}

.sidebar-close {
background: none;
border: none;
color: var(--color-gray-500);
cursor: pointer;
padding: var(--spacing-2);
border-radius: 0;
transition: color 0.2s ease;
}

.sidebar-close:hover {
color: var(--color-black);
}

.sidebar-nav {
padding: var(--spacing-6);
}

.sidebar-section {
margin-bottom: var(--spacing-8);
}

.sidebar-section-title {
font-size: var(--font-size-xs);
font-weight: 600;
color: var(--color-gray-500);
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0 0 var(--spacing-4) 0;
}

.sidebar-item {
display: block;
padding: var(--spacing-3) var(--spacing-4);
color: var(--color-black);
text-decoration: none;
font-size: var(--font-size-sm);
font-weight: 400;
transition: all 0.2s ease;
border: none;
background: none;
width: 100%;
text-align: left;
cursor: pointer;
}

.sidebar-item:hover {
background-color: var(--color-gray-50);
color: var(--color-black);
}

.nav-link {
color: var(--color-black);
font-family: var(--font-primary);
font-weight: 500;
text-decoration: none;
transition: var(--transition-fast);
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-sm);
}

.nav-link:hover {
color: var(--color-gray-700);
background: var(--color-gray-50);
}

/* Swiss Alerts */
.alert {
padding: var(--spacing-4) var(--spacing-6);
border: var(--border-sm);
margin-bottom: var(--spacing-6);
background: var(--color-white);
box-shadow: var(--shadow-sm);
position: relative;
font-size: var(--font-size-sm);
font-weight: 500;
}

.alert::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
}

.alert-success {
border-left: 4px solid var(--color-gray-700);
background: var(--color-off-white);
color: var(--color-gray-800);
}

.alert-success::before {
background: var(--color-gray-700);
}

.alert-danger {
border-left: 4px solid var(--color-gray-700);
background: var(--color-off-white);
color: var(--color-gray-800);
}

.alert-danger::before {
background: var(--color-gray-700);
}

.alert-warning {
border-left: 4px solid var(--color-gray-700);
background: var(--color-off-white);
color: var(--color-gray-800);
}

.alert-warning::before {
background: var(--color-gray-700);
}

.alert-info {
border-left: 4px solid var(--color-gray-700);
background: var(--color-off-white);
color: var(--color-gray-800);
}

.alert-info::before {
background: var(--color-gray-700);
}

/* Swiss Layout */
.container {
max-width: var(--max-width-xl);
margin: 0 auto;
padding: 0 var(--container-padding);
}

.row {
display: flex;
flex-wrap: wrap;
margin: 0 calc(-1 * var(--spacing-4));
}

.col {
flex: 1;
padding: 0 var(--spacing-4);
}

.col-6 {
flex: 0 0 50%;
padding: 0 var(--spacing-4);
}

.col-12 {
flex: 0 0 100%;
padding: 0 var(--spacing-4);
}

/* Swiss Spacing */
.mt-1 { margin-top: var(--spacing-1); }
.mt-2 { margin-top: var(--spacing-2); }
.mt-3 { margin-top: var(--spacing-3); }
.mt-4 { margin-top: var(--spacing-4); }
.mt-5 { margin-top: var(--spacing-5); }
.mt-6 { margin-top: var(--spacing-6); }
.mt-8 { margin-top: var(--spacing-8); }
.mt-10 { margin-top: var(--spacing-10); }
.mt-12 { margin-top: var(--spacing-12); }
.mt-16 { margin-top: var(--spacing-16); }
.mt-20 { margin-top: var(--spacing-20); }
.mt-24 { margin-top: var(--spacing-24); }
.mt-32 { margin-top: var(--spacing-32); }

.mb-1 { margin-bottom: var(--spacing-1); }
.mb-2 { margin-bottom: var(--spacing-2); }
.mb-3 { margin-bottom: var(--spacing-3); }
.mb-4 { margin-bottom: var(--spacing-4); }
.mb-5 { margin-bottom: var(--spacing-5); }
.mb-6 { margin-bottom: var(--spacing-6); }
.mb-8 { margin-bottom: var(--spacing-8); }
.mb-10 { margin-bottom: var(--spacing-10); }
.mb-12 { margin-bottom: var(--spacing-12); }
.mb-16 { margin-bottom: var(--spacing-16); }
.mb-20 { margin-bottom: var(--spacing-20); }
.mb-24 { margin-bottom: var(--spacing-24); }
.mb-32 { margin-bottom: var(--spacing-32); }

.pt-1 { padding-top: var(--spacing-1); }
.pt-2 { padding-top: var(--spacing-2); }
.pt-3 { padding-top: var(--spacing-3); }
.pt-4 { padding-top: var(--spacing-4); }
.pt-5 { padding-top: var(--spacing-5); }
.pt-6 { padding-top: var(--spacing-6); }
.pt-8 { padding-top: var(--spacing-8); }
.pt-10 { padding-top: var(--spacing-10); }
.pt-12 { padding-top: var(--spacing-12); }
.pt-16 { padding-top: var(--spacing-16); }
.pt-20 { padding-top: var(--spacing-20); }
.pt-24 { padding-top: var(--spacing-24); }
.pt-32 { padding-top: var(--spacing-32); }

.pb-1 { padding-bottom: var(--spacing-1); }
.pb-2 { padding-bottom: var(--spacing-2); }
.pb-3 { padding-bottom: var(--spacing-3); }
.pb-4 { padding-bottom: var(--spacing-4); }
.pb-5 { padding-bottom: var(--spacing-5); }
.pb-6 { padding-bottom: var(--spacing-6); }
.pb-8 { padding-bottom: var(--spacing-8); }
.pb-10 { padding-bottom: var(--spacing-10); }
.pb-12 { padding-bottom: var(--spacing-12); }
.pb-16 { padding-bottom: var(--spacing-16); }
.pb-20 { padding-bottom: var(--spacing-20); }
.pb-24 { padding-bottom: var(--spacing-24); }
.pb-32 { padding-bottom: var(--spacing-32); }

/* Swiss Text Utilities */
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
.text-black { color: var(--color-black); }
.text-gray-900 { color: var(--color-gray-900); }
.text-gray-700 { color: var(--color-gray-700); }
.text-gray-500 { color: var(--color-gray-500); }
.text-gray-300 { color: var(--color-gray-300); }
.text-white { color: var(--color-white); }

.font-weight-light { font-weight: 300; }
.font-weight-normal { font-weight: 400; }
.font-weight-medium { font-weight: 500; }
.font-weight-semibold { font-weight: 600; }
.font-weight-bold { font-weight: 700; }

.font-size-xs { font-size: var(--font-size-xs); }
.font-size-sm { font-size: var(--font-size-sm); }
.font-size-base { font-size: var(--font-size-base); }
.font-size-lg { font-size: var(--font-size-lg); }
.font-size-xl { font-size: var(--font-size-xl); }
.font-size-2xl { font-size: var(--font-size-2xl); }
.font-size-3xl { font-size: var(--font-size-3xl); }
.font-size-4xl { font-size: var(--font-size-4xl); }
.font-size-5xl { font-size: var(--font-size-5xl); }
.font-size-6xl { font-size: var(--font-size-6xl); }

/* Swiss Validation */
.field-validation-error {
color: var(--color-gray-700);
font-size: var(--font-size-sm);
margin-top: var(--spacing-1);
display: block;
font-family: var(--font-mono);
font-weight: 500;
}

.input-validation-error {
border-color: var(--color-gray-700) !important;
box-shadow: 0 0 0 1px var(--color-gray-700) !important;
}

.input-validation-error:focus {
outline: none;
border-color: var(--color-gray-800) !important;
box-shadow: 0 0 0 2px var(--color-gray-700) !important;
}

/* Swiss Tables */
.table {
width: 100%;
border-collapse: collapse;
background: var(--color-white);
border: var(--border-sm);
box-shadow: var(--shadow-sm);
}

.table th,
.table td {
padding: var(--spacing-4) var(--spacing-6);
text-align: left;
border-bottom: var(--border-sm);
color: var(--color-black);
font-family: var(--font-primary);
font-size: var(--font-size-sm);
}

.table th {
font-weight: 600;
background: var(--color-off-white);
color: var(--color-black);
letter-spacing: -0.01em;
}

.table tbody tr:hover {
background: var(--color-off-white);
}

/* Swiss Display Utilities */
.d-none { display: none !important; }
.d-block { display: block !important; }
.d-flex { display: flex !important; }
.flex-column { flex-direction: column !important; }
.justify-content-center { justify-content: center !important; }
.justify-content-between { justify-content: space-between !important; }
.align-items-center { align-items: center !important; }
.align-items-start { align-items: flex-start !important; }
.gap-1 { gap: var(--spacing-1) !important; }
.gap-2 { gap: var(--spacing-2) !important; }
.gap-3 { gap: var(--spacing-3) !important; }
.gap-4 { gap: var(--spacing-4) !important; }
.gap-6 { gap: var(--spacing-6) !important; }
.gap-8 { gap: var(--spacing-8) !important; }
.position-relative { position: relative !important; }
.position-absolute { position: absolute !important; }
.w-100 { width: 100% !important; }
.w-auto { width: auto !important; }
.h-100 { height: 100% !important; }
.h-auto { height: auto !important; }
.min-vh-100 { min-height: 100vh !important; }

/* Swiss Minimal Animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}

@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

.animate-fadeIn {
animation: fadeIn 0.3s ease-out;
}

.animate-slideUp {
animation: slideUp 0.4s ease-out;
}

/* Swiss Borders */
.border-none { border: none !important; }
.border-sm { border: var(--border-sm) !important; }
.border-md { border: var(--border-md) !important; }
.border-lg { border: var(--border-lg) !important; }
.border-t { border-top: var(--border-sm) !important; }
.border-b { border-bottom: var(--border-sm) !important; }
.border-l { border-left: var(--border-sm) !important; }
.border-r { border-right: var(--border-sm) !important; }

/* Swiss Backgrounds */
.bg-white { background: var(--color-white) !important; }
.bg-black { background: var(--color-black) !important; }
.bg-gray-50 { background: var(--color-gray-50) !important; }
.bg-gray-100 { background: var(--color-gray-100) !important; }
.bg-off-white { background: var(--color-off-white) !important; }

/* Swiss Sizing */
.w-8 { width: 2rem !important; }
.w-12 { width: 3rem !important; }
.w-16 { width: 4rem !important; }
.w-20 { width: 5rem !important; }
.w-24 { width: 6rem !important; }
.w-32 { width: 8rem !important; }
.w-48 { width: 12rem !important; }
.w-64 { width: 16rem !important; }

.h-8 { height: 2rem !important; }
.h-12 { height: 3rem !important; }
.h-16 { height: 4rem !important; }
.h-20 { height: 5rem !important; }
.h-24 { height: 6rem !important; }
.h-32 { height: 8rem !important; }
.h-48 { height: 12rem !important; }
.h-64 { height: 16rem !important; }

.rounded-none { border-radius: 0 !important; }
.rounded-sm { border-radius: 2px !important; }

/* Swiss Responsive Design */
@media (max-width: 768px) {
.navbar-container {
flex-direction: column;
gap: var(--spacing-4);
height: auto;
padding: var(--spacing-4);
}

text
.navbar-nav { gap: var(--spacing-4); flex-wrap: wrap; justify-content: center; } .container { padding: 0 var(--spacing-4); } h1 { font-size: var(--font-size-4xl); } h2 { font-size: var(--font-size-3xl); } h3 { font-size: var(--font-size-2xl); } h4 { font-size: var(--font-size-xl); } h5 { font-size: var(--font-size-lg); } .card-body { padding: var(--spacing-8); } .card-header { padding: var(--spacing-6) var(--spacing-8); } .btn { padding: var(--spacing-3) var(--spacing-6); font-size: var(--font-size-sm); } .form-control, .form-select { padding: var(--spacing-3) var(--spacing-3); } .row { margin: 0 calc(-1 * var(--spacing-2)); } .col, .col-6, .col-12 { padding: 0 var(--spacing-2); }

}

@media (max-width: 480px) {
.navbar-brand {
font-size: var(--font-size-base);
}

text
.nav-link { font-size: var(--font-size-xs); padding: var(--spacing-1) var(--spacing-2); } .btn { width: 100%; justify-content: center; }

}

/* Swiss Utility Classes */
.text-uppercase { text-transform: uppercase; }
.text-lowercase { text-transform: lowercase; }
.text-capitalize { text-transform: capitalize; }
.text-nowrap { white-space: nowrap; }
.text-break { word-break: break-word; }

.overflow-hidden { overflow: hidden; }
.overflow-auto { overflow: auto; }
.overflow-scroll { overflow: scroll; }

.cursor-pointer { cursor: pointer; }
.cursor-default { cursor: default; }

.transition-none { transition: none !important; }

/* Form Validation Styles */
.field-error {
color: var(--color-gray-700);
font-size: var(--font-size-xs);
margin-top: var(--spacing-1);
display: block;
}

.input-validation-error {
border-color: var(--color-gray-700) !important;
background-color: var(--color-gray-50) !important;
}

/* Alert Styles */
.alert {
padding: var(--spacing-4);
margin-bottom: var(--spacing-4);
border: var(--border-sm);
border-radius: 0;
display: flex;
align-items: center;
gap: var(--spacing-3);
}

.alert-success {
background-color: var(--color-gray-50);
border-color: var(--color-gray-300);
color: var(--color-gray-700);
}

.alert-error {
background-color: var(--color-gray-50);
border-color: var(--color-gray-400);
color: var(--color-gray-800);
}

.alert-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}

/* Enhanced Button Styles */
.btn-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}

.btn-warning {
background-color: var(--color-gray-600);
color: var(--color-white);
border: 1px solid var(--color-gray-600);
}

.btn-warning:hover {
background-color: var(--color-gray-700);
border-color: var(--color-gray-700);
}

.btn-success {
background-color: var(--color-gray-800);
color: var(--color-white);
border: 1px solid var(--color-gray-800);
}

.btn-success:hover {
background-color: var(--color-black);
border-color: var(--color-black);
}

.btn-error {
background-color: var(--color-gray-800);
color: var(--color-white);
border: 1px solid var(--color-gray-800);
}

.btn-error:hover {
background-color: var(--color-black);
border-color: var(--color-black);
}

/* Table Styles */
.table {
width: 100%;
border-collapse: collapse;
border: var(--border-sm);
}

.table th,
.table td {
padding: var(--spacing-4);
text-align: left;
border-bottom: var(--border-sm);
}

.table th {
background-color: var(--color-gray-50);
font-weight: 600;
color: var(--color-gray-700);
text-transform: uppercase;
font-size: var(--font-size-xs);
}

.table tbody tr:hover {
background-color: var(--color-gray-50);
}

.table-responsive {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}

/* Additional Form Styles */
.form-input {
width: 100%;
padding: var(--spacing-3);
border: var(--border-sm);
background-color: var(--color-white);
color: var(--color-black);
font-family: var(--font-primary);
font-size: var(--font-size-base);
transition: all 0.2s ease;
}

.form-input:focus {
outline: 2px solid var(--color-gray-300);
outline-offset: -2px;
border-color: var(--color-gray-800);
}

.form-input::placeholder {
color: var(--color-gray-400);
}

.form-input[readonly] {
background-color: var(--color-gray-100);
cursor: not-allowed;
}

/* Layout System */
.row {
display: flex;
flex-wrap: wrap;
margin: 0 calc(-1 * var(--spacing-4));
}

.col {
flex: 1;
padding: 0 var(--spacing-4);
}

.col-md-8 {
flex: 0 0 66.666667%;
max-width: 66.666667%;
padding: 0 var(--spacing-4);
}

.col-md-4 {
flex: 0 0 33.333333%;
max-width: 33.333333%;
padding: 0 var(--spacing-4);
}

@media (max-width: 768px) {
.col-md-8,
.col-md-4 {
flex: 0 0 100%;
max-width: 100%;
}
}
.transition-fast { transition: var(--transition-fast) !important; }
.transition-normal { transition: var(--transition-normal) !important; }

.shadow-none { box-shadow: none !important; }
.shadow-sm { box-shadow: var(--shadow-sm) !important; }
.shadow-md { box-shadow: var(--shadow-md) !important; }
.shadow-lg { box-shadow: var(--shadow-lg) !important; }

.opacity-50 { opacity: 0.5 !important; }
.opacity-75 { opacity: 0.75 !important; }
.opacity-100 { opacity: 1 !important; }

.z-0 { z-index: 0 !important; }
.z-10 { z-index: 10 !important; }
.z-20 { z-index: 20 !important; }
.z-30 { z-index: 30 !important; }
.z-50 { z-index: 50 !important; }

/* Swiss Loading State */
.loading {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid var(--color-gray-300);
border-top-color: var(--color-black);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}

@keyframes spin {
to { transform: rotate(360deg); }
}

/* Swiss Focus Styles */
:focus-visible {
outline: 2px solid var(--color-gray-700);
outline-offset: 2px;
}

/* Swiss Print Styles */
@media print {
body {
background: var(--color-white);
color: var(--color-black);
}

text
.navbar, .btn { display: none; } .card { border: var(--border-sm); box-shadow: none; }

}

/* Swiss Link Hover Effects */
.hover-underline {
position: relative;
transition: var(--transition-fast);
}

.hover-underline:hover {
color: var(--color-gray-700);
}

.hover-underline::after {
content: '';
position: absolute;
width: 0;
height: 1px;
bottom: -1px;
left: 0;
background: var(--color-black);
transition: var(--transition-normal);
}

.hover-underline:hover::after {
width: 100%;
}

الفكرة

Planning responsive role-based menu

I’ll need to summarize the plan to rearrange the code for role-based menus. I think roles will control visibility, and on desktop, menu items will be inline, but for mobile, they collapse into a drawer with a toggle button. CSS will handle the responsiveness, ARIA attributes will ensure accessibility, and I'll adapt the Razor helpers. The goal is to show concise, responsive code.

Implementing mobile sidebar toggle

I can reuse the existing sidebar element for the mobile menu and add a hamburger button for mobile. The sidebar already holds role-based items, so I'll just toggle it with a function. On desktop, I'll display a horizontal menu and profile dropdown. For mobile, the menu collapses, only showing the hamburger. We'll update the CSS and add proper ARIA attributes for accessibility.

Planning desktop and mobile roles menu

For desktop, I’ll add a horizontal role-based menu with items like Dashboard and Reports in the navbar, but should ensure they're not repeated. On mobile, the sidebar will be used instead, with minimal CSS tweaks for responsiveness and ARIA requirements. I'll provide a step-by-step implementation plan and "drop-in" code for both inline menus and sidebar toggling. I'll ensure correct media queries and focus behavior while keeping it simple and clear.

Crafting responsive menu code

I’ll ensure the links are correctly wired and ARIA attributes toggling works. In the desktop navbar, I’ll add role-based links, ensuring the simple submenu for admins, and use "<button>" for the hamburger on mobile. I’ll write CSS to control visibility and place the hamburger before the user dropdown. On mobile, I’ll make sure the sidebar slides left, avoiding any conflicts with the existing styles. I'll also lock body scrolling if necessary.

Thought for 27s

الإجابة

sweet — you already have the role helpers and most of the styling. Here’s a tight plan and the exact changes to make it work cleanly on both desktop and mobile.

Plan (positioning & behavior)

  • Desktop (≥768px): show a horizontal role-based menu to the left of the user dropdown inside the right side of the navbar. Items appear only if the user has the role. No collapsing here.
  • Mobile (<768px): hide the horizontal menu and show a hamburger button on the right of the brand (actually in the right cluster, just before the user avatar). Tapping it slides in the existing left sidebar (which already has role-based sections) as the mobile menu.
  • Accessibility: the hamburger toggles aria-expanded, ESC closes the drawer, clicking a link in the drawer closes it. Focus isn’t hard-trapped (keeps code light) but the drawer has clear close affordances.
  • Single source of truth: we keep both desktop and mobile menus in sync via the same role checks (you already have the sidebar role blocks; we add a compact desktop strip using the same helpers).

1) Update the navbar markup

Drop this inside .navbar-container, between the brand and the existing .navbar-nav:

cshtml
<!-- Desktop role-based links (hidden on mobile) --> <div class="navbar-links desktop-only"> @if (User.Identity.IsAuthenticated) { @* Administrator *@ @if (HasRole("Administrator")) { <a class="nav-link" href="@Url.Action("Index","Dashboard")">Dashboard</a> <div class="nav-divider" aria-hidden="true"></div> <div class="nav-group"> <a class="nav-link" href="@Url.Action("Index","UserManagement")">Users</a> <a class="nav-link" href="@Url.Action("CreateUser","UserManagement")">Create</a> </div> } @* Manager or Admin *@ @if (HasAnyRole("Manager","Administrator")) { <a class="nav-link" href="@Url.Action("Index","Reports")">Reports</a> } @* Operations or Admin *@ @if (HasAnyRole("Operations","Administrator")) { <a class="nav-link" href="@Url.Action("Index","Process")">Processes</a> } @* Accounts or Admin *@ @if (HasAnyRole("Accounts","Administrator")) { <a class="nav-link" href="@Url.Action("Index","Finance")">Finance</a> } @* Store or Admin *@ @if (HasAnyRole("Store","Administrator")) { <a class="nav-link" href="@Url.Action("Index","Inventory")">Inventory</a> } } </div>

Then add a hamburger just before the existing user dropdown inside .navbar-nav:

cshtml
<!-- Mobile hamburger (hidden on desktop) --> <button class="nav-toggle mobile-only" type="button" aria-label="Open menu" aria-controls="sidebar" aria-expanded="false" onclick="toggleSidebar(event)"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"> <path d="M3 6H17M3 10H17M3 14H17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> </button>

The existing sidebar you already have becomes the mobile menu; no duplicate markup needed.


2) Minimal CSS additions

Add below your CSS (keeps your Swiss system intact):

css
/* Desktop/Mobile visibility toggles */ .desktop-only { display: flex; align-items: center; gap: var(--spacing-4); } .mobile-only { display: none; } /* tiny divider for Desktop menu grouping */ .nav-divider { width: 1px; height: 20px; background: var(--color-gray-200); margin: 0 var(--spacing-2); } .nav-group { display: inline-flex; gap: var(--spacing-2); } /* Hamburger button */ .nav-toggle { display: inline-flex; align-items: center; justify-content: center; padding: var(--spacing-2) var(--spacing-4); border: var(--border-sm); background: var(--color-white); cursor: pointer; transition: var(--transition-fast); } .nav-toggle:hover { background: var(--color-gray-50); } /* Responsive switch */ @media (max-width: 768px) { .desktop-only { display: none; } .mobile-only { display: inline-flex; } }

(No conflict with your existing queries; .navbar-container already changes layout under 768px.)


3) JavaScript to drive the mobile drawer

You referenced toggleSidebar() but didn’t define it. Add this below your existing scripts:

html
<script> // Sidebar (mobile menu) toggle + a11y function toggleSidebar(event) { if (event) { event.preventDefault(); event.stopPropagation(); } const sidebar = document.getElementById('sidebar'); const toggler = document.querySelector('.nav-toggle'); if (!sidebar) return; const isOpen = sidebar.classList.contains('show'); if (isOpen) { sidebar.classList.remove('show'); document.body.style.overflow = ''; if (toggler) toggler.setAttribute('aria-expanded', 'false'); } else { sidebar.classList.add('show'); document.body.style.overflow = 'hidden'; // prevent background scroll if (toggler) toggler.setAttribute('aria-expanded', 'true'); // close when clicking outside setTimeout(() => { document.addEventListener('click', closeSidebarOutside, { once: true }); }, 0); } } function closeSidebarOutside(e) { const sidebar = document.getElementById('sidebar'); const toggler = document.querySelector('.nav-toggle'); if (!sidebar) return; const clickInsideSidebar = sidebar.contains(e.target); const clickOnToggler = toggler && toggler.contains(e.target); if (!clickInsideSidebar && !clickOnToggler) { sidebar.classList.remove('show'); document.body.style.overflow = ''; if (toggler) toggler.setAttribute('aria-expanded', 'false'); } } // Close on ESC document.addEventListener('keydown', function (event) { if (event.key === 'Escape') { const sidebar = document.getElementById('sidebar'); if (sidebar && sidebar.classList.contains('show')) { sidebar.classList.remove('show'); document.body.style.overflow = ''; const toggler = document.querySelector('.nav-toggle'); if (toggler) toggler.setAttribute('aria-expanded', 'false'); } } }); // Close the drawer after tapping a link (mobile) document.addEventListener('click', function (e) { const sidebar = document.getElementById('sidebar'); if (!sidebar || !sidebar.classList.contains('show')) return; const link = e.target.closest('a.sidebar-item, a.sidebar-link, .sidebar a'); if (link) { sidebar.classList.remove('show'); document.body.style.overflow = ''; const toggler = document.querySelector('.nav-toggle'); if (toggler) toggler.setAttribute('aria-expanded', 'false'); } }); </script>

4) (Optional) polish for the sidebar close area

If you want a tap-to-dismiss overlay on mobile, add:

css
/* Overlay shown when sidebar is open (mobile) */ .sidebar-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.1); opacity: 0; pointer-events: none; transition: var(--transition-normal); z-index: 998; } .sidebar.show + .sidebar-overlay { opacity: 1; pointer-events: auto; } @media (min-width: 769px) { .sidebar-overlay { display: none; } }

And right after <aside id="sidebar" ...></aside> place:

html
<div class="sidebar-overlay" onclick="toggleSidebar(event)"></div>

Why this layout feels “right”

  • Desktop flow: Brand (left) → role links (center-right) → user profile (far-right). This mirrors common enterprise apps, keeps Fitts’ Law-friendly targets on the far right for account actions.
  • Mobile flow: Brand (top), with hamburger next to profile on the right. Primary nav moves to an off-canvas drawer (your Sidebar) which is easier to scroll and houses the richer, grouped sections you already defined by role.
  • No duplication of logic: both places rely on your HasRole/HasAnyRole helpers; links are canonical MVC routes.

If you want, I can also convert the desktop links into a compact “More ▾” megamenu for Admins with many items—but the above keeps it minimal and Swiss-clean.

مشاركة هذا السؤال والجواب