Skip to content

Commit

Permalink
feat: add permission table and create role flyout (#219)
Browse files Browse the repository at this point in the history
* feat: add permission table and create role flyout

* feat: display number of members

* refactor: refactor to use form

* chore(cms): add permissions to create role form

* feat: add validation

* fix: implement requested changes

Co-authored-by: Kati Frantz <bahdcoder@gmail.com>
  • Loading branch information
Placeholder30 and bahdcoder authored Dec 17, 2021
1 parent 5f5b1fa commit 2c54b9b
Show file tree
Hide file tree
Showing 3 changed files with 396 additions and 2 deletions.
2 changes: 2 additions & 0 deletions packages/cms/pages/settings/roles-and-permissions/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import RolesAndPermissions from './roles-and-permissions'
export default RolesAndPermissions
Original file line number Diff line number Diff line change
@@ -0,0 +1,392 @@
import React, { FunctionComponent, useEffect, useMemo, useState } from 'react'

import { EuiBasicTable } from '@tensei/eui/lib/components/basic_table'
import {
EuiFlyout,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiFlyoutFooter
} from '@tensei/eui/lib/components/flyout'
import { EuiTitle } from '@tensei/eui/lib/components/title'
import { useGeneratedHtmlId } from '@tensei/eui/lib/services/accessibility'
import { EuiButton, EuiButtonEmpty } from '@tensei/eui/lib/components/button'
import { EuiFieldText, EuiTextArea } from '@tensei/eui/lib/components/form'
import { EuiText } from '@tensei/eui/lib/components/text'
import { EuiFlexItem, EuiFlexGroup } from '@tensei/eui/lib/components/flex'
import {
EuiSwitch,
EuiForm,
EuiFormRow
} from '@tensei/eui/lib/components/form/'
import { EuiSpacer } from '@tensei/eui/lib/components/spacer/'
import styled from 'styled-components'
import { ResourceContract } from '@tensei/components'
import slugify from 'speakingurl'
interface CreateRoleForm {
name: string
description: string
slug: string
adminPermissions: number[]
}

interface AdminPermission {
id: number
name: string
slug: string
}

interface CreateRoleFormProps {
createRoleForm: CreateRoleForm
setCreateRoleForm: (form: CreateRoleForm) => void
fetchAdminRoles: () => void
}

const RolesAndPermissionWrapper = styled.div`
padding: 20px 40px 0 40px;
`
const TableHeading = styled.div`
display: flex;
justify-content: space-between;
padding-left: 7px;
`
const AccordionWrapper = styled.div`
margin-bottom: 20px;
`
const AccordionHeader = styled.div`
height: 46px;
width: 100%;
padding: 16px 14px;
border: 1px solid #c9d3db;
background-color: #f5f7f9;
border-radius: 6px;
`
const Accordionbody = styled.div``
const AccordionItem = styled.div`
padding: 20px 20px;
border-left: 0.4px solid #c9d3db;
border-right: 0.4px solid #c9d3db;
border-bottom: 0.4px solid #c9d3db;
height: 46px;
`

interface PermissionProps {
permission: string
resource: ResourceContract
allPermissions: AdminPermission[]
createRoleForm: CreateRoleFormProps
}

const Switch: React.FC<{
onChange?: (value: boolean) => void
}> = ({ onChange }) => {
const [checked, setChecked] = useState(false)

return (
<EuiSwitch
label="enable"
showLabel={false}
checked={checked}
onChange={() => {
setChecked(!checked)
onChange?.(!checked)
}}
/>
)
}

const AccordionPermissions: React.FC<PermissionProps> = ({
permission: action,
resource,
allPermissions,
createRoleForm: { createRoleForm, setCreateRoleForm }
}) => {
function onSwitchChange(checked: boolean) {
const selectedPermission = allPermissions.find(
permission =>
permission.slug === `${action.toLowerCase()}:${resource.slug}`
)

if (!selectedPermission) {
return
}

if (createRoleForm.adminPermissions.includes(selectedPermission.id)) {
setCreateRoleForm({
...createRoleForm,
adminPermissions: createRoleForm.adminPermissions.filter(
permission => permission !== selectedPermission?.id
)
})
} else {
setCreateRoleForm({
...createRoleForm,
adminPermissions: [
...createRoleForm.adminPermissions,
selectedPermission.id
]
})
}
}

return (
<AccordionItem>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiText size="s">Can {action}</EuiText>
<Switch onChange={onSwitchChange} />
</EuiFlexGroup>
</AccordionItem>
)
}

const FlyoutAccordion: React.FC<{
createRoleForm: CreateRoleFormProps
allPermissions: AdminPermission[]
}> = ({ createRoleForm, allPermissions }) => {
const resources = window.Tensei.state.resources
const [selectedPermission, setSelectedPermission] = useState<any>([])

return (
<>
{resources.map(resource => (
<AccordionWrapper>
<AccordionHeader>{resource.name}</AccordionHeader>
<Accordionbody>
{['Create', 'Index', 'Update', 'Delete'].map(permission => (
<>
<AccordionPermissions
resource={resource}
permission={permission}
allPermissions={allPermissions}
createRoleForm={createRoleForm}
/>
</>
))}
</Accordionbody>
</AccordionWrapper>
))}
</>
)
}
interface ErrObj {
name: string
slug: string
}

const RolesFlyout: React.FC<{
setIsFlyoutOpen: (open: boolean) => void
createRoleForm: CreateRoleFormProps
allPermissions: AdminPermission[]
}> = ({
setIsFlyoutOpen,
createRoleForm: { createRoleForm, setCreateRoleForm, fetchAdminRoles },
allPermissions
}) => {
const flyoutHeadingId = useGeneratedHtmlId()
const [errors, setErrors] = useState<ErrObj>({ name: '', slug: '' })

const closeFlyout = () => {
setIsFlyoutOpen(false)
}

const createARole = async () => {
const [response, error] = await window.Tensei.api.post(
'admin-roles',
createRoleForm
)
if (error) {
let errObj = { name: '', slug: '' }
const errArray = error.response?.data.errors

errArray.forEach((err: any) => {
errObj = { ...errObj, [err.field]: err.message }
})

setErrors(errObj)
return
}
fetchAdminRoles()
closeFlyout()
}

return (
<EuiFlyout onClose={closeFlyout}>
<EuiFlyoutHeader hasBorder aria-labelledby={flyoutHeadingId}>
<EuiTitle>
<h2 id={flyoutHeadingId}> Create custom role </h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiForm component="form" isInvalid={!!errors?.name}>
<EuiFormRow
label="Name"
error={errors?.name}
isInvalid={!!errors?.name}
fullWidth
>
<EuiFieldText
onChange={event =>
setCreateRoleForm({
...createRoleForm,
name: event.target.value,
slug: slugify(event.target.value)
})
}
name="name"
fullWidth
/>
</EuiFormRow>

<EuiFormRow label="Description" fullWidth>
<EuiTextArea
name="description"
onChange={event =>
setCreateRoleForm({
...createRoleForm,
description: event.target.value
})
}
fullWidth
/>
</EuiFormRow>
<EuiSpacer size="xl" />
<FlyoutAccordion
createRoleForm={{
createRoleForm,
setCreateRoleForm,
fetchAdminRoles
}}
allPermissions={allPermissions}
/>
</EuiForm>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" flush="left" onClick={closeFlyout}>
Cancel
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
onClick={() => {
createARole()
}}
fill
>
Create Role
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
)
}

const RolesTable: React.FC = () => {
const [createRoleForm, setCreateRoleForm] = useState<CreateRoleForm>({
name: '',
description: '',
slug: '',
adminPermissions: []
})
const [allPermissions, setAllPermissions] = useState<AdminPermission[]>([])
const [adminRoles, setAdminRoles] = useState<any>([])
const [loading, setLoading] = useState(true)
const [isFlyoutOpen, setIsFlyoutOpen] = useState(false)

const columns = [
{
field: 'role',
name: 'Role '
},
{
field: 'members',
name: 'Members'
}
]

const fetchAdminRoles = async () => {
const [response] = await window.Tensei.api.get('admin-roles')

setAdminRoles(response?.data.data)

setLoading(false)
}
useEffect(() => {
fetchAdminRoles()
}, [])

async function fetchPermissions() {
const [response] = await window.Tensei.api.get<{
data: AdminPermission[]
}>('admin-permissions')
if (response !== null) {
setAllPermissions(response.data.data)
}
}

useEffect(() => {
fetchPermissions()
}, [])

const renderedItems = adminRoles.map((item: any) => {
const numberOfMembers = adminRoles.filter(
(memberItem: any) => item.name === memberItem.name
)
const member = numberOfMembers.length > 1 ? 'members' : 'member'
return {
...item,
role: item.name,
members: `${numberOfMembers.length} ${member}`
}
})

return (
<>
<TableHeading>
<EuiTitle>
<h1>
{adminRoles.length > 1
? `${adminRoles.length} existing roles`
: adminRoles.length === 1
? `1 existing role`
: `No roles`}
</h1>
</EuiTitle>
<EuiButton
onClick={() => {
setIsFlyoutOpen(true)
}}
>
create a new role
</EuiButton>
</TableHeading>
{isFlyoutOpen ? (
<RolesFlyout
setIsFlyoutOpen={setIsFlyoutOpen}
allPermissions={allPermissions}
createRoleForm={{
createRoleForm,
setCreateRoleForm,
fetchAdminRoles
}}
/>
) : null}
<EuiBasicTable
items={renderedItems}
columns={columns}
loading={loading}
/>
</>
)
}

const RolesAndPermissions: FunctionComponent = () => {
return (
<RolesAndPermissionWrapper>
<RolesTable />
</RolesAndPermissionWrapper>
)
}

export default RolesAndPermissions
Loading

0 comments on commit 2c54b9b

Please sign in to comment.