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:
104
client/src/contexts/AuthContext.jsx
Normal file
104
client/src/contexts/AuthContext.jsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { createContext, useState, useEffect, useContext } from 'react'
|
||||
import { api } from '../utils/api'
|
||||
|
||||
const AuthContext = createContext(null)
|
||||
|
||||
export function AuthProvider({ children }) {
|
||||
const [user, setUser] = useState(null)
|
||||
const [permissions, setPermissions] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
checkAuth()
|
||||
}, [])
|
||||
|
||||
const checkAuth = async () => {
|
||||
try {
|
||||
const userData = await api.get('/auth/me')
|
||||
setUser(userData)
|
||||
const perms = await api.get('/auth/permissions')
|
||||
setPermissions(perms)
|
||||
} catch (err) {
|
||||
console.log('Not authenticated')
|
||||
setUser(null)
|
||||
setPermissions(null)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const login = async (email, password) => {
|
||||
const response = await api.post('/auth/login', { email, password })
|
||||
setUser(response.user)
|
||||
// Load permissions after login
|
||||
try {
|
||||
const perms = await api.get('/auth/permissions')
|
||||
setPermissions(perms)
|
||||
} catch (err) {
|
||||
console.error('Failed to load permissions:', err)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
await api.post('/auth/logout')
|
||||
} catch (err) {
|
||||
console.error('Logout error:', err)
|
||||
} finally {
|
||||
setUser(null)
|
||||
setPermissions(null)
|
||||
window.location.href = '/login'
|
||||
}
|
||||
}
|
||||
|
||||
// Check if current user owns a resource
|
||||
const isOwner = (resource) => {
|
||||
if (!user || !resource) return false
|
||||
return resource.created_by_user_id === user.id
|
||||
}
|
||||
|
||||
// Check if current user is assigned to a resource
|
||||
const isAssignedTo = (resource) => {
|
||||
if (!user || !resource) return false
|
||||
const teamMemberId = user.team_member_id || user.teamMemberId
|
||||
if (!teamMemberId) return false
|
||||
const assignedTo = resource.assigned_to || resource.assignedTo
|
||||
return assignedTo === teamMemberId
|
||||
}
|
||||
|
||||
// Check if user can edit a specific resource (owns it, assigned to it, or has role)
|
||||
const canEditResource = (type, resource) => {
|
||||
if (!permissions) return false
|
||||
if (type === 'post') return permissions.canEditAnyPost || isOwner(resource) || isAssignedTo(resource)
|
||||
if (type === 'task') return permissions.canEditAnyTask || isOwner(resource) || isAssignedTo(resource)
|
||||
return false
|
||||
}
|
||||
|
||||
const canDeleteResource = (type, resource) => {
|
||||
if (!permissions) return false
|
||||
if (type === 'post') return permissions.canDeleteAnyPost || isOwner(resource) || isAssignedTo(resource)
|
||||
if (type === 'task') return permissions.canDeleteAnyTask || isOwner(resource) || isAssignedTo(resource)
|
||||
return false
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{
|
||||
user, loading, permissions,
|
||||
login, logout, checkAuth,
|
||||
isOwner, canEditResource, canDeleteResource,
|
||||
}}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const context = useContext(AuthContext)
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within AuthProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export default AuthContext
|
||||
Reference in New Issue
Block a user