mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: enhance role management API with organization-specific role creation and retrieval, including comprehensive RBAC checks for permissions
This commit is contained in:
parent
3ce019abec
commit
531e1863c0
10 changed files with 2174 additions and 32 deletions
548
apps/web/components/Objects/Modals/Dash/OrgRoles/EditRole.tsx
Normal file
548
apps/web/components/Objects/Modals/Dash/OrgRoles/EditRole.tsx
Normal file
|
|
@ -0,0 +1,548 @@
|
|||
'use client'
|
||||
import FormLayout, {
|
||||
FormField,
|
||||
FormLabelAndMessage,
|
||||
Input,
|
||||
Textarea,
|
||||
} from '@components/Objects/StyledElements/Form/Form'
|
||||
import * as Form from '@radix-ui/react-form'
|
||||
import { useOrg } from '@components/Contexts/OrgContext'
|
||||
import React from 'react'
|
||||
import { updateRole } from '@services/roles/roles'
|
||||
import { mutate } from 'swr'
|
||||
import { getAPIUrl } from '@services/config/config'
|
||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||
import { useFormik } from 'formik'
|
||||
import toast from 'react-hot-toast'
|
||||
import { Shield, BookOpen, Users, UserCheck, FolderOpen, Building, FileText, Activity, Settings, Monitor, CheckSquare, Square } from 'lucide-react'
|
||||
|
||||
type EditRoleProps = {
|
||||
role: {
|
||||
id: number,
|
||||
name: string,
|
||||
description: string,
|
||||
rights: any
|
||||
}
|
||||
setEditRoleModal: any
|
||||
}
|
||||
|
||||
interface Rights {
|
||||
courses: {
|
||||
action_create: boolean;
|
||||
action_read: boolean;
|
||||
action_read_own: boolean;
|
||||
action_update: boolean;
|
||||
action_update_own: boolean;
|
||||
action_delete: boolean;
|
||||
action_delete_own: boolean;
|
||||
};
|
||||
users: {
|
||||
action_create: boolean;
|
||||
action_read: boolean;
|
||||
action_update: boolean;
|
||||
action_delete: boolean;
|
||||
};
|
||||
usergroups: {
|
||||
action_create: boolean;
|
||||
action_read: boolean;
|
||||
action_update: boolean;
|
||||
action_delete: boolean;
|
||||
};
|
||||
collections: {
|
||||
action_create: boolean;
|
||||
action_read: boolean;
|
||||
action_update: boolean;
|
||||
action_delete: boolean;
|
||||
};
|
||||
organizations: {
|
||||
action_create: boolean;
|
||||
action_read: boolean;
|
||||
action_update: boolean;
|
||||
action_delete: boolean;
|
||||
};
|
||||
coursechapters: {
|
||||
action_create: boolean;
|
||||
action_read: boolean;
|
||||
action_update: boolean;
|
||||
action_delete: boolean;
|
||||
};
|
||||
activities: {
|
||||
action_create: boolean;
|
||||
action_read: boolean;
|
||||
action_update: boolean;
|
||||
action_delete: boolean;
|
||||
};
|
||||
roles: {
|
||||
action_create: boolean;
|
||||
action_read: boolean;
|
||||
action_update: boolean;
|
||||
action_delete: boolean;
|
||||
};
|
||||
dashboard: {
|
||||
action_access: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const validate = (values: any) => {
|
||||
const errors: any = {}
|
||||
|
||||
if (!values.name) {
|
||||
errors.name = 'Required'
|
||||
} else if (values.name.length < 2) {
|
||||
errors.name = 'Name must be at least 2 characters'
|
||||
}
|
||||
|
||||
if (!values.description) {
|
||||
errors.description = 'Required'
|
||||
} else if (values.description.length < 10) {
|
||||
errors.description = 'Description must be at least 10 characters'
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
const predefinedRoles = {
|
||||
'Admin': {
|
||||
name: 'Admin',
|
||||
description: 'Full platform control with all permissions',
|
||||
rights: {
|
||||
courses: { action_create: true, action_read: true, action_read_own: true, action_update: true, action_update_own: true, action_delete: true, action_delete_own: true },
|
||||
users: { action_create: true, action_read: true, action_update: true, action_delete: true },
|
||||
usergroups: { action_create: true, action_read: true, action_update: true, action_delete: true },
|
||||
collections: { action_create: true, action_read: true, action_update: true, action_delete: true },
|
||||
organizations: { action_create: true, action_read: true, action_update: true, action_delete: true },
|
||||
coursechapters: { action_create: true, action_read: true, action_update: true, action_delete: true },
|
||||
activities: { action_create: true, action_read: true, action_update: true, action_delete: true },
|
||||
roles: { action_create: true, action_read: true, action_update: true, action_delete: true },
|
||||
dashboard: { action_access: true }
|
||||
}
|
||||
},
|
||||
'Course Manager': {
|
||||
name: 'Course Manager',
|
||||
description: 'Can manage courses, chapters, and activities',
|
||||
rights: {
|
||||
courses: { action_create: true, action_read: true, action_read_own: true, action_update: true, action_update_own: true, action_delete: false, action_delete_own: true },
|
||||
users: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
usergroups: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
collections: { action_create: true, action_read: true, action_update: true, action_delete: false },
|
||||
organizations: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
coursechapters: { action_create: true, action_read: true, action_update: true, action_delete: false },
|
||||
activities: { action_create: true, action_read: true, action_update: true, action_delete: false },
|
||||
roles: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
dashboard: { action_access: true }
|
||||
}
|
||||
},
|
||||
'Instructor': {
|
||||
name: 'Instructor',
|
||||
description: 'Can create and manage their own courses',
|
||||
rights: {
|
||||
courses: { action_create: true, action_read: true, action_read_own: true, action_update: false, action_update_own: true, action_delete: false, action_delete_own: true },
|
||||
users: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
usergroups: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
collections: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
organizations: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
coursechapters: { action_create: true, action_read: true, action_update: false, action_delete: false },
|
||||
activities: { action_create: true, action_read: true, action_update: false, action_delete: false },
|
||||
roles: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
dashboard: { action_access: true }
|
||||
}
|
||||
},
|
||||
'Viewer': {
|
||||
name: 'Viewer',
|
||||
description: 'Read-only access to courses and content',
|
||||
rights: {
|
||||
courses: { action_create: false, action_read: true, action_read_own: true, action_update: false, action_update_own: false, action_delete: false, action_delete_own: false },
|
||||
users: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
usergroups: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
collections: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
organizations: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
coursechapters: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
activities: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
roles: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
dashboard: { action_access: true }
|
||||
}
|
||||
},
|
||||
'Content Creator': {
|
||||
name: 'Content Creator',
|
||||
description: 'Can create and edit content but not manage users',
|
||||
rights: {
|
||||
courses: { action_create: true, action_read: true, action_read_own: true, action_update: true, action_update_own: true, action_delete: false, action_delete_own: false },
|
||||
users: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
usergroups: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
collections: { action_create: true, action_read: true, action_update: true, action_delete: false },
|
||||
organizations: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
coursechapters: { action_create: true, action_read: true, action_update: true, action_delete: false },
|
||||
activities: { action_create: true, action_read: true, action_update: true, action_delete: false },
|
||||
roles: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
dashboard: { action_access: true }
|
||||
}
|
||||
},
|
||||
'User Manager': {
|
||||
name: 'User Manager',
|
||||
description: 'Can manage users and user groups',
|
||||
rights: {
|
||||
courses: { action_create: false, action_read: true, action_read_own: true, action_update: false, action_update_own: false, action_delete: false, action_delete_own: false },
|
||||
users: { action_create: true, action_read: true, action_update: true, action_delete: true },
|
||||
usergroups: { action_create: true, action_read: true, action_update: true, action_delete: true },
|
||||
collections: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
organizations: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
coursechapters: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
activities: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
roles: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
dashboard: { action_access: true }
|
||||
}
|
||||
},
|
||||
'Moderator': {
|
||||
name: 'Moderator',
|
||||
description: 'Can moderate content and manage activities',
|
||||
rights: {
|
||||
courses: { action_create: false, action_read: true, action_read_own: true, action_update: false, action_update_own: false, action_delete: false, action_delete_own: false },
|
||||
users: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
usergroups: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
collections: { action_create: false, action_read: true, action_update: true, action_delete: false },
|
||||
organizations: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
coursechapters: { action_create: false, action_read: true, action_update: true, action_delete: false },
|
||||
activities: { action_create: false, action_read: true, action_update: true, action_delete: false },
|
||||
roles: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
dashboard: { action_access: true }
|
||||
}
|
||||
},
|
||||
'Analyst': {
|
||||
name: 'Analyst',
|
||||
description: 'Read-only access with analytics capabilities',
|
||||
rights: {
|
||||
courses: { action_create: false, action_read: true, action_read_own: true, action_update: false, action_update_own: false, action_delete: false, action_delete_own: false },
|
||||
users: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
usergroups: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
collections: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
organizations: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
coursechapters: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
activities: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
roles: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
dashboard: { action_access: true }
|
||||
}
|
||||
},
|
||||
'Guest': {
|
||||
name: 'Guest',
|
||||
description: 'Limited access for external users',
|
||||
rights: {
|
||||
courses: { action_create: false, action_read: true, action_read_own: false, action_update: false, action_update_own: false, action_delete: false, action_delete_own: false },
|
||||
users: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
usergroups: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
collections: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
organizations: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
coursechapters: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
activities: { action_create: false, action_read: true, action_update: false, action_delete: false },
|
||||
roles: { action_create: false, action_read: false, action_update: false, action_delete: false },
|
||||
dashboard: { action_access: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function EditRole(props: EditRoleProps) {
|
||||
const org = useOrg() as any;
|
||||
const session = useLHSession() as any
|
||||
const access_token = session?.data?.tokens?.access_token;
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
||||
const [rights, setRights] = React.useState<Rights>(props.role.rights || {})
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
name: props.role.name,
|
||||
description: props.role.description,
|
||||
org_id: org.id,
|
||||
rights: props.role.rights || {}
|
||||
},
|
||||
validate,
|
||||
onSubmit: async (values) => {
|
||||
const toastID = toast.loading("Updating...")
|
||||
setIsSubmitting(true)
|
||||
|
||||
// Ensure rights object is properly structured
|
||||
const formattedRights = {
|
||||
courses: {
|
||||
action_create: rights.courses?.action_create || false,
|
||||
action_read: rights.courses?.action_read || false,
|
||||
action_read_own: rights.courses?.action_read_own || false,
|
||||
action_update: rights.courses?.action_update || false,
|
||||
action_update_own: rights.courses?.action_update_own || false,
|
||||
action_delete: rights.courses?.action_delete || false,
|
||||
action_delete_own: rights.courses?.action_delete_own || false
|
||||
},
|
||||
users: {
|
||||
action_create: rights.users?.action_create || false,
|
||||
action_read: rights.users?.action_read || false,
|
||||
action_update: rights.users?.action_update || false,
|
||||
action_delete: rights.users?.action_delete || false
|
||||
},
|
||||
usergroups: {
|
||||
action_create: rights.usergroups?.action_create || false,
|
||||
action_read: rights.usergroups?.action_read || false,
|
||||
action_update: rights.usergroups?.action_update || false,
|
||||
action_delete: rights.usergroups?.action_delete || false
|
||||
},
|
||||
collections: {
|
||||
action_create: rights.collections?.action_create || false,
|
||||
action_read: rights.collections?.action_read || false,
|
||||
action_update: rights.collections?.action_update || false,
|
||||
action_delete: rights.collections?.action_delete || false
|
||||
},
|
||||
organizations: {
|
||||
action_create: rights.organizations?.action_create || false,
|
||||
action_read: rights.organizations?.action_read || false,
|
||||
action_update: rights.organizations?.action_update || false,
|
||||
action_delete: rights.organizations?.action_delete || false
|
||||
},
|
||||
coursechapters: {
|
||||
action_create: rights.coursechapters?.action_create || false,
|
||||
action_read: rights.coursechapters?.action_read || false,
|
||||
action_update: rights.coursechapters?.action_update || false,
|
||||
action_delete: rights.coursechapters?.action_delete || false
|
||||
},
|
||||
activities: {
|
||||
action_create: rights.activities?.action_create || false,
|
||||
action_read: rights.activities?.action_read || false,
|
||||
action_update: rights.activities?.action_update || false,
|
||||
action_delete: rights.activities?.action_delete || false
|
||||
},
|
||||
roles: {
|
||||
action_create: rights.roles?.action_create || false,
|
||||
action_read: rights.roles?.action_read || false,
|
||||
action_update: rights.roles?.action_update || false,
|
||||
action_delete: rights.roles?.action_delete || false
|
||||
},
|
||||
dashboard: {
|
||||
action_access: rights.dashboard?.action_access || false
|
||||
}
|
||||
}
|
||||
|
||||
const res = await updateRole(props.role.id, {
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
org_id: values.org_id,
|
||||
rights: formattedRights
|
||||
}, access_token)
|
||||
if (res.status === 200) {
|
||||
setIsSubmitting(false)
|
||||
mutate(`${getAPIUrl()}roles/org/${org.id}`)
|
||||
props.setEditRoleModal(false)
|
||||
toast.success("Updated role", {id:toastID})
|
||||
} else {
|
||||
setIsSubmitting(false)
|
||||
toast.error("Couldn't update role", {id:toastID})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const handleRightChange = (section: keyof Rights, action: string, value: boolean) => {
|
||||
setRights(prev => ({
|
||||
...prev,
|
||||
[section]: {
|
||||
...prev[section],
|
||||
[action]: value
|
||||
} as any
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSelectAll = (section: keyof Rights, value: boolean) => {
|
||||
setRights(prev => ({
|
||||
...prev,
|
||||
[section]: Object.keys(prev[section]).reduce((acc, key) => ({
|
||||
...acc,
|
||||
[key]: value
|
||||
}), {} as any)
|
||||
}))
|
||||
}
|
||||
|
||||
const handlePredefinedRole = (roleKey: string) => {
|
||||
const role = predefinedRoles[roleKey as keyof typeof predefinedRoles]
|
||||
if (role) {
|
||||
formik.setFieldValue('name', role.name)
|
||||
formik.setFieldValue('description', role.description)
|
||||
setRights(role.rights as Rights)
|
||||
}
|
||||
}
|
||||
|
||||
const PermissionSection = ({ title, icon: Icon, section, permissions }: { title: string, icon: any, section: keyof Rights, permissions: string[] }) => {
|
||||
const sectionRights = rights[section] as any
|
||||
const allSelected = permissions.every(perm => sectionRights[perm])
|
||||
const someSelected = permissions.some(perm => sectionRights[perm]) && !allSelected
|
||||
|
||||
return (
|
||||
<div className="border border-gray-200 rounded-lg p-4 mb-4 bg-white shadow-sm">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-3 gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Icon className="w-4 h-4 text-gray-500" />
|
||||
<h3 className="font-semibold text-gray-800 text-sm sm:text-base">{title}</h3>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSelectAll(section, !allSelected)}
|
||||
className="flex items-center space-x-2 text-sm text-blue-600 hover:text-blue-700 font-medium self-start sm:self-auto transition-colors"
|
||||
>
|
||||
{allSelected ? <CheckSquare className="w-4 h-4" /> : someSelected ? <Square className="w-4 h-4" /> : <Square className="w-4 h-4" />}
|
||||
<span className="hidden sm:inline">{allSelected ? 'Deselect All' : 'Select All'}</span>
|
||||
<span className="sm:hidden">{allSelected ? 'Deselect' : 'Select'}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
{permissions.map((permission) => (
|
||||
<label key={permission} className="flex items-center space-x-2 cursor-pointer p-2 rounded-md hover:bg-gray-50 transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={rights[section]?.[permission as keyof typeof rights[typeof section]] || false}
|
||||
onChange={(e) => handleRightChange(section, permission, e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500 focus:ring-2"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 capitalize">
|
||||
{permission.replace('action_', '').replace('_', ' ')}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-3 max-w-6xl mx-auto px-2 sm:px-0">
|
||||
<FormLayout onSubmit={formik.handleSubmit}>
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4 sm:gap-6">
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
<FormField name="name">
|
||||
<FormLabelAndMessage label="Role Name" message={formik.errors.name} />
|
||||
<Form.Control asChild>
|
||||
<Input
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name}
|
||||
type="text"
|
||||
required
|
||||
placeholder="e.g., Course Manager"
|
||||
className="w-full"
|
||||
/>
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<FormField name="description">
|
||||
<FormLabelAndMessage label="Description" message={formik.errors.description} />
|
||||
<Form.Control asChild>
|
||||
<Textarea
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.description}
|
||||
required
|
||||
placeholder="Describe what this role can do..."
|
||||
className="w-full"
|
||||
/>
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<div className="mt-6">
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-4">Predefined Rights</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{Object.keys(predefinedRoles).map((roleKey) => (
|
||||
<button
|
||||
key={roleKey}
|
||||
type="button"
|
||||
onClick={() => handlePredefinedRole(roleKey)}
|
||||
className="p-3 border border-gray-200 rounded-lg hover:border-blue-300 hover:bg-blue-50 transition-all duration-200 text-left bg-white shadow-sm hover:shadow-md"
|
||||
>
|
||||
<div className="font-medium text-gray-900 text-sm sm:text-base">{predefinedRoles[roleKey as keyof typeof predefinedRoles].name}</div>
|
||||
<div className="text-xs sm:text-sm text-gray-500 mt-1">{predefinedRoles[roleKey as keyof typeof predefinedRoles].description}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-4">Permissions</h3>
|
||||
|
||||
<PermissionSection
|
||||
title="Courses"
|
||||
icon={BookOpen}
|
||||
section="courses"
|
||||
permissions={['action_create', 'action_read', 'action_read_own', 'action_update', 'action_update_own', 'action_delete', 'action_delete_own']}
|
||||
/>
|
||||
|
||||
<PermissionSection
|
||||
title="Users"
|
||||
icon={Users}
|
||||
section="users"
|
||||
permissions={['action_create', 'action_read', 'action_update', 'action_delete']}
|
||||
/>
|
||||
|
||||
<PermissionSection
|
||||
title="User Groups"
|
||||
icon={UserCheck}
|
||||
section="usergroups"
|
||||
permissions={['action_create', 'action_read', 'action_update', 'action_delete']}
|
||||
/>
|
||||
|
||||
<PermissionSection
|
||||
title="Collections"
|
||||
icon={FolderOpen}
|
||||
section="collections"
|
||||
permissions={['action_create', 'action_read', 'action_update', 'action_delete']}
|
||||
/>
|
||||
|
||||
<PermissionSection
|
||||
title="Organizations"
|
||||
icon={Building}
|
||||
section="organizations"
|
||||
permissions={['action_create', 'action_read', 'action_update', 'action_delete']}
|
||||
/>
|
||||
|
||||
<PermissionSection
|
||||
title="Course Chapters"
|
||||
icon={FileText}
|
||||
section="coursechapters"
|
||||
permissions={['action_create', 'action_read', 'action_update', 'action_delete']}
|
||||
/>
|
||||
|
||||
<PermissionSection
|
||||
title="Activities"
|
||||
icon={Activity}
|
||||
section="activities"
|
||||
permissions={['action_create', 'action_read', 'action_update', 'action_delete']}
|
||||
/>
|
||||
|
||||
<PermissionSection
|
||||
title="Roles"
|
||||
icon={Shield}
|
||||
section="roles"
|
||||
permissions={['action_create', 'action_read', 'action_update', 'action_delete']}
|
||||
/>
|
||||
|
||||
<PermissionSection
|
||||
title="Dashboard"
|
||||
icon={Monitor}
|
||||
section="dashboard"
|
||||
permissions={['action_access']}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row justify-end space-y-2 sm:space-y-0 sm:space-x-3 mt-6 pt-6 border-t border-gray-200">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => props.setEditRoleModal(false)}
|
||||
className="px-4 py-2 text-gray-600 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors w-full sm:w-auto font-medium"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<Form.Submit asChild>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="px-4 py-2 bg-black text-white rounded-md hover:bg-gray-800 transition-colors disabled:opacity-50 w-full sm:w-auto font-medium shadow-sm"
|
||||
>
|
||||
{isSubmitting ? 'Updating...' : 'Update Role'}
|
||||
</button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
</FormLayout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditRole
|
||||
Loading…
Add table
Add a link
Reference in a new issue