Marketing Hub: RBAC, i18n (AR/EN), tasks overhaul, team/user merge, tutorial
Features: - Full RBAC with 3 roles (superadmin/manager/contributor) - Ownership tracking on posts, tasks, campaigns, projects - Task system: assign to anyone, filter combobox, visibility scoping - Team members merged into users table (single source of truth) - Post thumbnails on kanban cards from attachments - Publication link validation before publishing - Interactive onboarding tutorial with Settings restart - Full Arabic/English i18n with RTL layout support - Language toggle in sidebar, IBM Plex Sans Arabic font - Brand-based visibility filtering for non-superadmins - Manager can only create contributors - Profile completion flow for new users - Cookie-based sessions (express-session + SQLite)
This commit is contained in:
134
client/src/components/Header.jsx
Normal file
134
client/src/components/Header.jsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Bell, ChevronDown, LogOut, Settings, User, Shield } from 'lucide-react'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
|
||||
const pageTitles = {
|
||||
'/': 'Dashboard',
|
||||
'/posts': 'Post Production',
|
||||
'/assets': 'Assets',
|
||||
'/campaigns': 'Campaigns',
|
||||
'/finance': 'Finance',
|
||||
'/projects': 'Projects',
|
||||
'/tasks': 'My Tasks',
|
||||
'/team': 'Team',
|
||||
'/users': 'User Management',
|
||||
}
|
||||
|
||||
const ROLE_INFO = {
|
||||
superadmin: { label: 'Superadmin', color: 'bg-purple-100 text-purple-700', icon: '👑' },
|
||||
manager: { label: 'Manager', color: 'bg-blue-100 text-blue-700', icon: '📊' },
|
||||
contributor: { label: 'Contributor', color: 'bg-green-100 text-green-700', icon: '✏️' },
|
||||
}
|
||||
|
||||
export default function Header() {
|
||||
const { user, logout } = useAuth()
|
||||
const [showDropdown, setShowDropdown] = useState(false)
|
||||
const dropdownRef = useRef(null)
|
||||
const location = useLocation()
|
||||
|
||||
const pageTitle = pageTitles[location.pathname] ||
|
||||
(location.pathname.startsWith('/projects/') ? 'Project Details' :
|
||||
location.pathname.startsWith('/campaigns/') ? 'Campaign Details' : 'Page')
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
||||
setShowDropdown(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [])
|
||||
|
||||
const getInitials = (name) => {
|
||||
if (!name) return '?'
|
||||
return name.split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase()
|
||||
}
|
||||
|
||||
const roleInfo = ROLE_INFO[user?.role] || ROLE_INFO.contributor
|
||||
|
||||
return (
|
||||
<header className="h-16 bg-white border-b border-border flex items-center justify-between px-6 shrink-0 sticky top-0 z-20">
|
||||
{/* Page title */}
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-text-primary">{pageTitle}</h2>
|
||||
</div>
|
||||
|
||||
{/* Right side */}
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Notifications */}
|
||||
<button className="relative p-2 rounded-lg hover:bg-surface-tertiary text-text-secondary hover:text-text-primary transition-colors">
|
||||
<Bell className="w-5 h-5" />
|
||||
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-red-500 rounded-full"></span>
|
||||
</button>
|
||||
|
||||
{/* User menu */}
|
||||
<div ref={dropdownRef} className="relative">
|
||||
<button
|
||||
onClick={() => setShowDropdown(!showDropdown)}
|
||||
className="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-surface-tertiary transition-colors"
|
||||
>
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-white text-xs font-semibold ${
|
||||
user?.role === 'superadmin'
|
||||
? 'bg-gradient-to-br from-purple-500 to-pink-500'
|
||||
: 'bg-gradient-to-br from-blue-500 to-indigo-500'
|
||||
}`}>
|
||||
{getInitials(user?.name)}
|
||||
</div>
|
||||
<div className="text-left hidden sm:block">
|
||||
<p className="text-sm font-medium text-text-primary">
|
||||
{user?.name || 'User'}
|
||||
</p>
|
||||
<p className={`text-[10px] font-medium ${roleInfo.color.split(' ')[1]}`}>
|
||||
{roleInfo.icon} {roleInfo.label}
|
||||
</p>
|
||||
</div>
|
||||
<ChevronDown className={`w-4 h-4 text-text-tertiary transition-transform ${showDropdown ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
|
||||
{showDropdown && (
|
||||
<div className="absolute right-0 top-full mt-2 w-64 bg-white rounded-xl shadow-lg border border-border overflow-hidden animate-scale-in">
|
||||
{/* User info */}
|
||||
<div className="px-4 py-3 border-b border-border-light bg-surface-secondary">
|
||||
<p className="text-sm font-semibold text-text-primary">{user?.name}</p>
|
||||
<p className="text-xs text-text-tertiary">{user?.email}</p>
|
||||
<div className={`inline-flex items-center gap-1 text-[10px] font-medium px-2 py-0.5 rounded-full mt-2 ${roleInfo.color}`}>
|
||||
<span>{roleInfo.icon}</span>
|
||||
{roleInfo.label}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Menu items */}
|
||||
<div className="py-2">
|
||||
{user?.role === 'superadmin' && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowDropdown(false)
|
||||
window.location.href = '/users'
|
||||
}}
|
||||
className="w-full flex items-center gap-3 px-4 py-2.5 hover:bg-surface-secondary transition-colors text-left"
|
||||
>
|
||||
<Shield className="w-4 h-4 text-text-tertiary" />
|
||||
<span className="text-sm text-text-primary">User Management</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowDropdown(false)
|
||||
logout()
|
||||
}}
|
||||
className="w-full flex items-center gap-3 px-4 py-2.5 hover:bg-red-50 transition-colors text-left group"
|
||||
>
|
||||
<LogOut className="w-4 h-4 text-text-tertiary group-hover:text-red-500" />
|
||||
<span className="text-sm text-text-primary group-hover:text-red-500">Sign Out</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user