mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: implement comprehensive RBAC checks for courses, chapters, collections, and activities, enhancing user rights management and security documentation
This commit is contained in:
parent
887046203e
commit
3ce019abec
22 changed files with 1788 additions and 598 deletions
|
|
@ -1,23 +1,108 @@
|
|||
'use client'
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Link from 'next/link'
|
||||
import { Package2, Settings } from 'lucide-react'
|
||||
import { Package2, Settings, Crown, Shield, User, Users, Building, LogOut, User as UserIcon, Home, ChevronDown } from 'lucide-react'
|
||||
import UserAvatar from '@components/Objects/UserAvatar'
|
||||
import useAdminStatus from '@components/Hooks/useAdminStatus'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import { getUriWithoutOrg } from '@services/config/config'
|
||||
import Tooltip from '@components/Objects/StyledElements/Tooltip/Tooltip'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@components/ui/dropdown-menu"
|
||||
import { signOut } from 'next-auth/react'
|
||||
|
||||
interface RoleInfo {
|
||||
name: string;
|
||||
icon: React.ReactNode;
|
||||
bgColor: string;
|
||||
textColor: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const HeaderProfileBox = () => {
|
||||
const session = useLHSession() as any
|
||||
const isUserAdmin = useAdminStatus()
|
||||
const { isAdmin, loading, userRoles, rights } = useAdminStatus()
|
||||
const org = useOrg() as any
|
||||
|
||||
useEffect(() => { }
|
||||
, [session])
|
||||
|
||||
const userRoleInfo = useMemo((): RoleInfo | null => {
|
||||
if (!userRoles || userRoles.length === 0) return null;
|
||||
|
||||
// Find the highest priority role for the current organization
|
||||
const orgRoles = userRoles.filter((role: any) => role.org.id === org?.id);
|
||||
|
||||
if (orgRoles.length === 0) return null;
|
||||
|
||||
// Sort by role priority (admin > maintainer > instructor > user)
|
||||
const sortedRoles = orgRoles.sort((a: any, b: any) => {
|
||||
const getRolePriority = (role: any) => {
|
||||
if (role.role.role_uuid === 'role_global_admin' || role.role.id === 1) return 4;
|
||||
if (role.role.role_uuid === 'role_global_maintainer' || role.role.id === 2) return 3;
|
||||
if (role.role.role_uuid === 'role_global_instructor' || role.role.id === 3) return 2;
|
||||
return 1;
|
||||
};
|
||||
return getRolePriority(b) - getRolePriority(a);
|
||||
});
|
||||
|
||||
const highestRole = sortedRoles[0];
|
||||
|
||||
// Define role configurations based on actual database roles
|
||||
const roleConfigs: { [key: string]: RoleInfo } = {
|
||||
'role_global_admin': {
|
||||
name: 'ADMIN',
|
||||
icon: <Crown size={12} />,
|
||||
bgColor: 'bg-purple-600',
|
||||
textColor: 'text-white',
|
||||
description: 'Full platform control with all permissions'
|
||||
},
|
||||
'role_global_maintainer': {
|
||||
name: 'MAINTAINER',
|
||||
icon: <Shield size={12} />,
|
||||
bgColor: 'bg-blue-600',
|
||||
textColor: 'text-white',
|
||||
description: 'Mid-level manager with wide permissions'
|
||||
},
|
||||
'role_global_instructor': {
|
||||
name: 'INSTRUCTOR',
|
||||
icon: <Users size={12} />,
|
||||
bgColor: 'bg-green-600',
|
||||
textColor: 'text-white',
|
||||
description: 'Can manage their own content'
|
||||
},
|
||||
'role_global_user': {
|
||||
name: 'USER',
|
||||
icon: <User size={12} />,
|
||||
bgColor: 'bg-gray-500',
|
||||
textColor: 'text-white',
|
||||
description: 'Read-Only Learner'
|
||||
}
|
||||
};
|
||||
|
||||
// Determine role based on role_uuid or id
|
||||
let roleKey = 'role_global_user'; // default
|
||||
if (highestRole.role.role_uuid) {
|
||||
roleKey = highestRole.role.role_uuid;
|
||||
} else if (highestRole.role.id === 1) {
|
||||
roleKey = 'role_global_admin';
|
||||
} else if (highestRole.role.id === 2) {
|
||||
roleKey = 'role_global_maintainer';
|
||||
} else if (highestRole.role.id === 3) {
|
||||
roleKey = 'role_global_instructor';
|
||||
}
|
||||
|
||||
return roleConfigs[roleKey] || roleConfigs['role_global_user'];
|
||||
}, [userRoles, org?.id]);
|
||||
|
||||
return (
|
||||
<ProfileArea>
|
||||
{session.status == 'unauthenticated' && (
|
||||
|
|
@ -35,35 +120,73 @@ export const HeaderProfileBox = () => {
|
|||
)}
|
||||
{session.status == 'authenticated' && (
|
||||
<AccountArea className="space-x-0">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className='flex items-center space-x-2' >
|
||||
<p className='text-sm capitalize'>{session.data.user.username}</p>
|
||||
{isUserAdmin.isAdmin && <div className="text-[10px] bg-rose-300 px-2 font-bold rounded-md shadow-inner py-1">ADMIN</div>}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Tooltip
|
||||
content={"Your Owned Courses"}
|
||||
sideOffset={15}
|
||||
side="bottom"
|
||||
>
|
||||
<Link className="text-gray-600" href={'/dash/user-account/owned'}>
|
||||
<Package2 size={14} />
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={"Your Settings"}
|
||||
sideOffset={15}
|
||||
side="bottom"
|
||||
>
|
||||
<Link className="text-gray-600" href={'/dash'}>
|
||||
<Settings size={14} />
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<UserAvatar border="border-4" rounded="rounded-lg" width={30} />
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="cursor-pointer flex items-center space-x-3 hover:bg-gray-50 rounded-lg p-2 transition-colors">
|
||||
<UserAvatar border="border-2" rounded="rounded-lg" width={30} />
|
||||
<div className="flex flex-col space-y-0">
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className='text-sm font-semibold text-gray-900 capitalize'>{session.data.user.username}</p>
|
||||
{userRoleInfo && userRoleInfo.name !== 'USER' && (
|
||||
<Tooltip
|
||||
content={userRoleInfo.description}
|
||||
sideOffset={15}
|
||||
side="bottom"
|
||||
>
|
||||
<div className={`text-[6px] ${userRoleInfo.bgColor} ${userRoleInfo.textColor} px-1 py-0.5 font-medium rounded-full flex items-center gap-0.5 w-fit`}>
|
||||
{userRoleInfo.icon}
|
||||
{userRoleInfo.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<p className='text-xs text-gray-500'>{session.data.user.email}</p>
|
||||
</div>
|
||||
<ChevronDown size={16} className="text-gray-500" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="end">
|
||||
<DropdownMenuLabel>
|
||||
<div className="flex items-center space-x-2">
|
||||
<UserAvatar border="border-2" rounded="rounded-full" width={24} />
|
||||
<div>
|
||||
<p className="text-sm font-medium">{session.data.user.username}</p>
|
||||
<p className="text-xs text-gray-500 capitalize">{session.data.user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{rights?.dashboard?.action_access && (
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/dash" className="flex items-center space-x-2">
|
||||
<Shield size={16} />
|
||||
<span>Dashboard</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/dash/user-account/settings/general" className="flex items-center space-x-2">
|
||||
<UserIcon size={16} />
|
||||
<span>User Settings</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/dash/user-account/owned" className="flex items-center space-x-2">
|
||||
<Package2 size={16} />
|
||||
<span>My Courses</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => signOut({ callbackUrl: '/' })}
|
||||
className="flex items-center space-x-2 text-red-600 focus:text-red-600"
|
||||
>
|
||||
<LogOut size={16} />
|
||||
<span>Sign Out</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</AccountArea>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue