re-structure to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 01:02:31 +02:00
parent 8242ec64ba
commit 7a0a13f9d5
10400 changed files with 46966 additions and 17223 deletions

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
import React from 'react';
const RoleDeleteAlert = React.lazy(
() => import('@/containers/Alerts/Roles/RoleDeleteAlert'),
);
/**
* Roles alerts
*/
export default [{ name: 'role-delete', component: RoleDeleteAlert }];

View File

@@ -0,0 +1,55 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import { useFormikContext } from 'formik';
import { Intent, Button } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T } from '@/components';
/**
* Role form floating actions.
* @returns {React.JSX}
*/
export function RoleFormFloatingActions() {
// Formik form context.
const { isSubmitting } = useFormikContext();
// History context.
const history = useHistory();
// Handle close click.
const handleCloseClick = () => {
history.go(-1);
};
return (
<RoleFormFloatingActionsRoot>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
type="submit"
style={{ minWidth: '90px' }}
>
<T id={'save'} />
</Button>
<Button onClick={handleCloseClick} disabled={isSubmitting}>
<T id={'cancel'} />
</Button>
</RoleFormFloatingActionsRoot>
);
}
const RoleFormFloatingActionsRoot = styled.div`
position: fixed;
bottom: 0;
width: 100%;
background: #fff;
padding: 14px 18px;
border-top: 1px solid #d2dde2;
box-shadow: 0px -1px 4px 0px rgb(0 0 0 / 5%);
.bp3-button {
margin-right: 10px;
}
`;

View File

@@ -0,0 +1,64 @@
// @ts-nocheck
import React from 'react';
import { ErrorMessage, FastField } from 'formik';
import { FormGroup, InputGroup, TextArea } from '@blueprintjs/core';
import { inputIntent } from '@/utils';
import { FormattedMessage as T, FieldRequiredHint, Card } from '@/components';
import { useAutofocus } from '@/hooks';
/**
* Role form header.
* @returns {React.JSX}
*/
export function RoleFormHeader() {
const roleNameFieldRef = useAutofocus();
return (
<Card>
{/* ---------- Name ---------- */}
<FastField name={'role_name'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={
<strong>
<T id={'roles.label.role_name'} />
</strong>
}
labelInfo={<FieldRequiredHint />}
className={'form-group--name'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="role_name" />}
inline={true}
>
<InputGroup
medium={true}
inputRef={(ref) => (roleNameFieldRef.current = ref)}
{...field}
/>
</FormGroup>
)}
</FastField>
{/* ---------- Description ---------- */}
<FastField name={'role_description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'role_description'} />}
inline={true}
>
<TextArea
growVertically={true}
height={280}
{...field}
placeholder="Max. 500 characters"
/>
</FormGroup>
)}
</FastField>
</Card>
);
}

View File

@@ -0,0 +1,17 @@
// @ts-nocheck
import React from 'react';
import { useFormikContext } from 'formik';
import { FormikObserver } from '@/components';
/**
* Role form observer.
* @returns {React.JSX}
*/
export function RoleFormObserver() {
const { values } = useFormikContext();
// Handles the form change.
const handleFormChange = () => {};
return <FormikObserver onChange={handleFormChange} values={values} />;
}

View File

@@ -0,0 +1,17 @@
// @ts-nocheck
import * as Yup from 'yup';
import intl from 'react-intl-universal';
import { DATATYPES_LENGTH } from '@/constants/dataTypes';
const Schema = Yup.object().shape({
role_name: Yup.string().required().label(intl.get('roles.label.role_name_')),
role_description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
permissions: Yup.object().shape({
subject: Yup.string(),
ability: Yup.string(),
value: Yup.boolean(),
}),
});
export const CreateRolesFormSchema = Schema;
export const EditRolesFormSchema = Schema;

View File

@@ -0,0 +1,109 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { useHistory } from 'react-router-dom';
import { Formik } from 'formik';
import { isEmpty } from 'lodash';
import { Intent } from '@blueprintjs/core';
import '@/style/pages/Preferences/Roles/Form.scss';
import { AppToaster, FormattedMessage as T } from '@/components';
import { CreateRolesFormSchema, EditRolesFormSchema } from './RolesForm.schema';
import { useRolesFormContext } from './RolesFormProvider';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import RolesFormContent from './RolesFormContent';
import {
getNewRoleInitialValues,
transformToArray,
transformToObject,
} from './utils';
import { handleDeleteErrors } from '../utils';
import { compose, transformToForm } from '@/utils';
const defaultValues = {
role_name: '',
role_description: '',
permissions: {},
serviceFullAccess: {},
};
/**
* Preferences - Roles Form.
*/
function RolesForm({
// #withDashboardActions
changePreferencesPageTitle,
}) {
// History context.
const history = useHistory();
// Role form context.
const {
isNewMode,
createRolePermissionMutate,
editRolePermissionMutate,
permissionsSchema,
role,
roleId,
} = useRolesFormContext();
// Initial values.
const initialValues = {
...defaultValues,
...(!isEmpty(role)
? transformToForm(transformToObject(role), defaultValues)
: getNewRoleInitialValues(permissionsSchema)),
};
React.useEffect(() => {
changePreferencesPageTitle(<T id={'roles.label'} />);
}, [changePreferencesPageTitle]);
// Handle the form submit.
const handleFormSubmit = (values, { setSubmitting }) => {
const permission = transformToArray(values);
const form = {
...values,
permissions: permission,
};
setSubmitting(true);
const onSuccess = () => {
AppToaster.show({
message: intl.get(
isNewMode
? 'roles.permission_schema.success_message'
: 'roles.permission_schema.upload_message',
),
intent: Intent.SUCCESS,
});
setSubmitting(false);
history.push('/preferences/users');
};
const onError = ({
response: {
data: { errors },
},
}) => {
setSubmitting(false);
handleDeleteErrors(errors);
};
if (isNewMode) {
createRolePermissionMutate(form).then(onSuccess).catch(onError);
} else {
editRolePermissionMutate([roleId, form]).then(onSuccess).catch(onError);
}
};
return (
<Formik
initialValues={initialValues}
validationSchema={isNewMode ? CreateRolesFormSchema : EditRolesFormSchema}
onSubmit={handleFormSubmit}
>
<RolesFormContent />
</Formik>
);
}
export default compose(withDashboardActions)(RolesForm);

View File

@@ -0,0 +1,23 @@
// @ts-nocheck
import React from 'react';
import { Form } from 'formik';
import { RoleFormHeader } from './RoleFormHeader';
import { RolesPermissionList } from './components';
import { RoleFormFloatingActions } from './RoleFormFloatingActions';
import { RoleFormObserver } from './RoleFormObserver';
/**
* Preferences - Roles Form content.
* @returns {React.JSX}
*/
export default function RolesFormContent() {
return (
<Form>
<RoleFormHeader />
<RolesPermissionList />
<RoleFormFloatingActions />
<RoleFormObserver />
</Form>
);
}

View File

@@ -0,0 +1,19 @@
// @ts-nocheck
import React from 'react';
import { useParams } from 'react-router-dom';
import { RolesFormProvider } from './RolesFormProvider';
import RolesForm from './RolesForm';
/**
* Roles Form page.
*/
export default function RolesFormPage() {
const { id } = useParams();
const idInteger = parseInt(id, 10);
return (
<RolesFormProvider roleId={idInteger}>
<RolesForm />
</RolesFormProvider>
);
}

View File

@@ -0,0 +1,74 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
import {
useCreateRolePermissionSchema,
useEditRolePermissionSchema,
usePermissionsSchema,
useRolePermission,
} from '@/hooks/query';
import PreferencesPageLoader from '@/containers/Preferences/PreferencesPageLoader';
const RolesFormContext = React.createContext();
/**
* Roles Form page provider.
*/
function RolesFormProvider({ roleId, ...props }) {
// Create and edit roles mutations.
const { mutateAsync: createRolePermissionMutate } =
useCreateRolePermissionSchema();
const { mutateAsync: editRolePermissionMutate } =
useEditRolePermissionSchema();
// Retrieve permissions schema.
const {
data: permissionsSchema,
isLoading: isPermissionsSchemaLoading,
isFetching: isPermissionsSchemaFetching,
} = usePermissionsSchema();
const { data: role, isLoading: isPermissionLoading } = useRolePermission(
roleId,
{
enabled: !!roleId,
},
);
// Detarmines whether the new or edit mode.
const isNewMode = !roleId;
// Provider state.
const provider = {
isNewMode,
roleId,
role,
permissionsSchema,
isPermissionsSchemaLoading,
isPermissionsSchemaFetching,
createRolePermissionMutate,
editRolePermissionMutate,
};
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ROLES_FORM,
)}
>
{isPermissionsSchemaLoading || isPermissionLoading ? (
<PreferencesPageLoader />
) : (
<RolesFormContext.Provider value={provider} {...props} />
)}
</div>
);
}
const useRolesFormContext = () => React.useContext(RolesFormContext);
export { RolesFormProvider, useRolesFormContext };

View File

@@ -0,0 +1,513 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import { Field } from 'formik';
import { Checkbox, Popover } from '@blueprintjs/core';
import {
getPermissionsSchema,
ModulePermissionsStyle,
} from '@/constants/permissionsSchema';
import { Card, If, ButtonLink, Choose, T } from '@/components';
import {
getSerivceColumnPermission,
getServiceExtraPermissions,
FULL_ACCESS_CHECKBOX_STATE,
handleCheckboxFullAccessChange,
handleCheckboxPermissionChange,
} from './utils';
// Module permissions context.
const ModulePermissionsContext = React.createContext();
const ModuleServiceContext = React.createContext();
/**
* Retrieves the module permissions provider.
* @returns {React.JSX}
*/
const useModulePermissionsProvider = () =>
React.useContext(ModulePermissionsContext);
/**
* Module permissions service context provider.
*/
const useModulePermissionsServiceProvider = () =>
React.useContext(ModuleServiceContext);
/**
* Module permissions context state provider.
* @returns {React.JSX}
*/
function ModulePermissionsProvider({ module, children }) {
return (
<ModulePermissionsContext.Provider value={{ module }}>
{children}
</ModulePermissionsContext.Provider>
);
}
/**
* Module permissions service context state provider.
* @returns {React.JSX}
*/
function ModulePermissionsServiceProvider({ service, children }) {
return (
<ModuleServiceContext.Provider value={{ service }}>
{children}
</ModuleServiceContext.Provider>
);
}
/**
* Permissions body columns.
* @returns {React.JSX}
*/
function PermissionBodyColumn({ column }) {
// Module permssions service context.
const { service } = useModulePermissionsServiceProvider();
// Retrieve the related permission of the given column key.
const permission = getSerivceColumnPermission(service, column.key);
// Display empty cell if the current column key has no related permissions.
if (!permission) {
return <td class={'permission-checkbox'}></td>;
}
return (
<td class={'permission-checkbox'}>
<Field
name={`permissions.${service.subject}/${permission.key}`}
type="checkbox"
>
{({ field, form }) => (
<PermissionCheckbox
inline={true}
{...field}
onChange={handleCheckboxPermissionChange(form, permission, service)}
/>
)}
</Field>
</td>
);
}
/**
*
* @returns {React.JSX}
*/
function ModulePermissionsTableColumns({ columns }) {
return columns.map((column) => <PermissionBodyColumn column={column} />);
}
/**
* Module columns permissions extra permissions popover.
* @returns {React.JSX}
*/
function ModuleExtraPermissionsPopover() {
const { service } = useModulePermissionsServiceProvider();
// Retrieve the extra permissions of the given service.
const extraPermissions = getServiceExtraPermissions(service);
return (
<Popover>
<MorePermissionsLink>
<T id={'permissions.more_permissions'} />
</MorePermissionsLink>
<ExtraPermissionsRoot>
{extraPermissions.map((permission) => (
<Field
name={`permissions.${service.subject}/${permission.key}`}
type="checkbox"
>
{({ form, field }) => (
<PermissionCheckbox
inline={true}
label={permission.label}
{...field}
onChange={handleCheckboxPermissionChange(
form,
permission,
service,
)}
/>
)}
</Field>
))}
</ExtraPermissionsRoot>
</Popover>
);
}
/**
* Module permissions extra permissions.
* @returns {React.JSX}
*/
function ModulePermissionExtraPermissions() {
const { service } = useModulePermissionsServiceProvider();
// Retrieve the extra permissions of the given service.
const extraPermissions = getServiceExtraPermissions(service);
return (
<td>
<If condition={extraPermissions.length > 0}>
<ModuleExtraPermissionsPopover />
</If>
</td>
);
}
/**
* Module permissions table head.
* @returns {React.JSX}
*/
function ModulePermissionsTableHead() {
const {
module: { serviceFullAccess, columns },
} = useModulePermissionsProvider();
return (
<thead>
<tr>
<th></th>
<If condition={serviceFullAccess}>
<th class={'full'}>
<T id={'permissions.column.full_access'} />
</th>
</If>
{columns.map((column) => (
<th class={'permission'}>{column.label}</th>
))}
<th></th>
</tr>
</thead>
);
}
/**
* Module permissions service full access.
* @returns {React.JSX}
*/
function ModulePermissionsServiceFullAccess() {
// Module permissions provider.
const { module } = useModulePermissionsProvider();
// Module service provider.
const { service } = useModulePermissionsServiceProvider();
return (
<If condition={module.serviceFullAccess}>
<td class="full-access-permission">
<Field name={`serviceFullAccess.${service.subject}`} type="checkbox">
{({ form, field }) => (
<PermissionCheckbox
inline={true}
{...field}
indeterminate={
field.value === FULL_ACCESS_CHECKBOX_STATE.INDETARMINE
}
onChange={handleCheckboxFullAccessChange(service, form)}
/>
)}
</Field>
</td>
</If>
);
}
/**
* Module permissions table body.
* @returns {React.JSX}
*/
function ModulePermissionsTableBody() {
const {
module: { services, columns },
} = useModulePermissionsProvider();
return (
<tbody>
{services.map((service) => (
<ModulePermissionsServiceProvider service={service}>
<tr>
<td className="service-label">{service.label} </td>
<ModulePermissionsServiceFullAccess />
<ModulePermissionsTableColumns columns={columns} />
<ModulePermissionExtraPermissions />
</tr>
</ModulePermissionsServiceProvider>
))}
</tbody>
);
}
/**
* Module permissions table.
* @returns {React.JSX}
*/
function ModulePermissionsTable() {
return (
<ModulePermissionsTableRoot>
<ModulePermissionsTableHead />
<ModulePermissionsTableBody />
</ModulePermissionsTableRoot>
);
}
/**
* Module vertical table cells.
* @returns {React.JSX}
*/
function ModuleVerticalTableCells() {
const { service } = useModulePermissionsServiceProvider();
return (
<td class={'permissions'}>
{service.permissions.map((permission) => (
<div>
<Field
name={`permissions.${service.subject}/${permission.key}`}
type="checkbox"
>
{({ form, field }) => (
<PermissionCheckbox
inline={true}
label={permission.label}
{...field}
onChange={handleCheckboxPermissionChange(
form,
permission,
service,
)}
/>
)}
</Field>
</div>
))}
</td>
);
}
/**
* Module permissions vertical services.
* @returns {React.JSX}
*/
function ModulePermissionsVerticalServices() {
const { module } = useModulePermissionsProvider();
return (
<ModulePermissionsVerticalServicesRoot>
<ModulePermissionsVerticalTable>
<tbody>
{module.services.map((service) => (
<ModulePermissionsServiceProvider service={service}>
<tr>
<td class={'service-label'}>{service.label} </td>
<ModuleVerticalTableCells />
</tr>
</ModulePermissionsServiceProvider>
))}
</tbody>
</ModulePermissionsVerticalTable>
</ModulePermissionsVerticalServicesRoot>
);
}
/**
* Module permissions body.
* @returns {React.JSX}
*/
function ModulePermissionsBody() {
const { module } = useModulePermissionsProvider();
return (
<ModulePermissionBodyRoot>
<Choose>
<Choose.When
condition={module.type === ModulePermissionsStyle.Vertical}
>
<ModulePermissionsVerticalServices />
</Choose.When>
<Choose.When condition={module.type === ModulePermissionsStyle.Columns}>
<ModulePermissionsTable />
</Choose.When>
</Choose>
</ModulePermissionBodyRoot>
);
}
/**
* Permissions module.
* @returns {React.JSX}
*/
function ModulePermissions({ module }) {
return (
<ModulePermissionsRoot>
<ModulePermissionsProvider module={module}>
<ModulePermissionHead>
<ModulePermissionTitle>{module.label} </ModulePermissionTitle>
</ModulePermissionHead>
<ModulePermissionsBody />
</ModulePermissionsProvider>
</ModulePermissionsRoot>
);
}
/**
* Permissions modules list.
* @return {React.JSX}
*/
export const RolesPermissionList = () => {
const permissions = getPermissionsSchema();
return (
<ModulesPermission>
{permissions.map((module) => (
<ModulePermissions module={module} />
))}
</ModulesPermission>
);
};
const PermissionCheckbox = styled(Checkbox)`
&.bp3-control.bp3-checkbox .bp3-control-indicator {
border-radius: 2px;
border-color: #555;
&,
&:before {
width: 15px;
height: 15px;
}
}
`;
const ModulesPermission = styled.div``;
const ModulePermissionsRoot = styled(Card)`
padding: 0 !important;
`;
const ModulePermissionHead = styled.div`
border-bottom: 1px solid #d9d9d9;
height: 38px;
padding: 0 15px;
display: flex;
`;
const ModulePermissionTitle = styled.div`
font-weight: 500;
font-size: 16px;
line-height: 38px;
color: #878787;
`;
const ModulePermissionBodyRoot = styled.div``;
const ModulePermissionsTableRoot = styled.table`
border-spacing: 0;
thead {
tr th {
font-weight: 400;
vertical-align: top;
&.full,
&.permission {
min-width: 70px;
}
&.full {
background-color: #fcfcfc;
}
}
}
thead,
tbody {
tr td,
tr th {
border-bottom: 1px solid #eee;
border-left: 1px solid #eee;
padding: 10px;
&:first-of-type {
border-left: 0;
}
}
tr:last-of-type td {
border-bottom: 0;
}
tr td:last-of-type,
tr th:last-of-type {
width: 100%;
}
}
tbody {
tr td.service-label {
min-width: 250px;
}
tr td {
.bp3-control.bp3-inline {
margin: 0;
}
&.full-access-permission {
background-color: #fcfcfc;
}
&.full-access-permission,
&.permission-checkbox {
text-align: center;
}
}
}
`;
const MorePermissionsLink = styled(ButtonLink)`
font-size: 12px;
`;
const ExtraPermissionsRoot = styled.div`
display: flex;
flex-direction: column;
padding: 15px;
`;
const ModulePermissionsVerticalServicesRoot = styled.div``;
const ModulePermissionsVerticalTable = styled.table`
border-spacing: 0;
tbody {
tr td {
padding: 10px;
vertical-align: top;
border-left: 1px solid #eee;
border-bottom: 1px solid #eee;
&.service-label {
min-width: 250px;
color: #333;
}
&:first-of-type {
border-left: 0;
}
&.permissions {
width: 100%;
}
}
tr:last-of-type td {
border-bottom: 0;
}
}
`;

View File

@@ -0,0 +1,313 @@
// @ts-nocheck
import { chain, isEmpty, castArray, memoize } from 'lodash';
import * as R from 'ramda';
import { DepGraph } from 'dependency-graph';
import {
getPermissionsSchema,
getPermissionsSchemaService,
getPermissionsSchemaServices,
} from '@/constants/permissionsSchema';
export const FULL_ACCESS_CHECKBOX_STATE = {
INDETARMINE: -1,
ON: true,
OFF: false,
};
/**
* Transformes the permissions object to array.
* @returns
*/
export const transformToArray = ({ permissions }) => {
return Object.keys(permissions).map((index) => {
const [value, key] = index.split('/');
return {
subject: value,
ability: key,
value: permissions[index],
};
});
};
function transformPermissions(permissions) {
return Object.keys(permissions).map((permissionKey) => {
const [subject, key] = permissionKey.split('/');
const value = permissions[permissionKey];
return { key, subject, value };
});
}
/**
* Transformes permissions array to object.
* @param {*} permissions -
* @returns
*/
export const transformPermissionsToObject = (permissions) => {
const output = {};
permissions.forEach((item) => {
output[`${item.subject}/${item.ability}`] = !!item.value;
});
return output;
};
/**
*
* @param {*} role
* @returns
*/
export const transformToObject = (role) => {
const permissions = transformPermissionsToObject(role.permissions);
const serviceFullAccess = getInitialServicesFullAccess(permissions);
return {
role_name: role.name,
role_description: role.description,
permissions,
serviceFullAccess,
};
};
export const getDefaultValuesFromSchema = (schema) => {
return schema
.map((item) => {
const abilities = [
...(item.abilities || []),
...(item.extra_abilities || []),
];
return abilities
.filter((ability) => ability.default)
.map((ability) => ({
subject: item.subject,
ability: ability.key,
value: ability.default,
}));
})
.flat();
};
/**
* Retrieve initial values of full access services.
* @param {*} formPermissions
* @returns
*/
export const getInitialServicesFullAccess = (formPermissions) => {
const services = getPermissionsSchemaServices();
return chain(services)
.map((service) => {
const { subject } = service;
const isFullChecked = isServiceFullChecked(subject, formPermissions);
const isFullUnchecked = isServiceFullUnchecked(subject, formPermissions);
const value = detarmineCheckboxState(isFullChecked, isFullUnchecked);
return [service.subject, value];
})
.fromPairs()
.value();
};
/**
*
* @param {*} schema
* @returns
*/
export const getNewRoleInitialValues = (schema) => {
const permissions = transformPermissionsToObject(
getDefaultValuesFromSchema(schema),
);
const serviceFullAccess = getInitialServicesFullAccess(permissions);
return {
permissions,
serviceFullAccess,
};
};
/**
*
* @param {*} service
* @param {*} columnKey
* @returns
*/
export function getSerivceColumnPermission(service, columnKey) {
return service.permissions.find((permission) => {
return permission.relatedColumn === columnKey;
});
}
/**
*
* @param {*} service
* @returns
*/
export function getServiceExtraPermissions(service) {
return service.permissions.filter((permission) => {
return !permission.relatedColumn;
});
}
/**
* Detarmines the given service subject is full permissions checked.
*/
export function isServiceFullChecked(subject, permissions) {
const serviceSchema = getPermissionsSchemaService(subject);
return serviceSchema.permissions.every(
(permission) => permissions[`${subject}/${permission.key}`],
);
}
/**
* Detarmines the given service subject is fully associated permissions unchecked.
* @param {string} subject -
* @param {Object} permissionsMap -
*/
export function isServiceFullUnchecked(subject, permissionsMap) {
const serviceSchema = getPermissionsSchemaService(subject);
return serviceSchema.permissions.every(
(permission) => !permissionsMap[`${subject}/${permission.key}`],
);
}
/**
* Handles permission checkbox change.
*/
export const handleCheckboxPermissionChange = R.curry(
(form, permission, service, event) => {
const { subject } = service;
const isChecked = event.currentTarget.checked;
const permissionsGraph = memoizedPermissionsGraph();
const dependencies = isChecked
? permissionsGraph.dependenciesOf(`${subject}/${permission.key}`)
: permissionsGraph.dependantsOf(`${subject}/${permission.key}`);
const newDependsPermiss = chain(dependencies)
.map((dep) => [dep, isChecked])
.fromPairs()
.value();
const newValues = {
...form.values,
permissions: {
...form.values.permissions,
[`${subject}/${permission.key}`]: isChecked,
...newDependsPermiss,
},
};
const isFullChecked = isServiceFullChecked(subject, newValues.permissions);
const isFullUnchecked = isServiceFullUnchecked(
subject,
newValues.permissions,
);
form.setFieldValue(`permissions.${subject}/${permission.key}`, isChecked);
form.setFieldValue(
`serviceFullAccess.${subject}`,
detarmineCheckboxState(isFullChecked, isFullUnchecked),
);
dependencies.forEach((depKey) => {
form.setFieldValue(`permissions.${depKey}`, isChecked);
});
},
);
/**
* Detarmines the permission checkbox state.
* @param {boolean} isFullChecked
* @param {boolean} isFullUnchecked
* @returns {FULL_ACCESS_CHECKBOX_STATE}
*/
function detarmineCheckboxState(isFullChecked, isFullUnchecked) {
return isFullChecked
? FULL_ACCESS_CHECKBOX_STATE.ON
: isFullUnchecked
? FULL_ACCESS_CHECKBOX_STATE.OFF
: FULL_ACCESS_CHECKBOX_STATE.INDETARMINE;
}
/**
* Retreive the service all permissions paths.
* @param {string} subject
* @returns {string[]}
*/
export function getServiceAllPermissionsPaths(subject) {
const service = getPermissionsSchemaService(subject);
return service.permissions.map(
(perm) => `permissions.${subject}/${perm.key}`,
);
}
/**
* Handle full access service checkbox change.
*/
export const handleCheckboxFullAccessChange = R.curry(
(service, form, event) => {
const isChecked = event.currentTarget.checked;
const permsPaths = getServiceAllPermissionsPaths(service.subject);
form.setFieldValue(`serviceFullAccess.${service.subject}`, isChecked);
permsPaths.forEach((permissionPath) => {
form.setFieldValue(
permissionPath,
isChecked
? FULL_ACCESS_CHECKBOX_STATE.ON
: FULL_ACCESS_CHECKBOX_STATE.OFF,
);
});
},
);
/**
* Retrieves all flatten modules permissions.
*/
export function getAllFlattenPermissionsSchema() {
const permissions = getPermissionsSchema();
return chain(permissions)
.map((module) => module.services)
.flatten()
.map((module) =>
module.permissions.map((permission) => ({
subject: module.subject,
...permission,
})),
)
.flatten()
.value();
}
/**
* Retrieve the permissions schema dependencies graph.
* @returns {DepGraph}
*/
export const getPermissionsSchemaGraph = () => {
const graph = new DepGraph();
const permissions = getAllFlattenPermissionsSchema();
permissions.forEach((permission) => {
graph.addNode(`${permission.subject}/${permission.key}`, permission);
});
const nodesOrder = graph.overallOrder();
nodesOrder.forEach((key) => {
const node = graph.getNodeData(key);
if (isEmpty(node.depend)) return;
const depends = castArray(node.depend);
depends.forEach((dependRelation) => {
const subject = dependRelation.subject || node.subject;
graph.addDependency(key, `${subject}/${dependRelation.key}`);
});
});
return graph;
};
const memoizedPermissionsGraph = memoize(getPermissionsSchemaGraph);

View File

@@ -0,0 +1,78 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { DataTable, AppToaster, TableSkeletonRows } from '@/components';
import { useRolesTableColumns, ActionsMenu } from './components';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import { useRolesContext } from './RolesListProvider';
import { compose } from '@/utils';
/**
* Roles data table.
*/
function RolesDataTable({
// #withAlertsActions
openAlert,
}) {
// History context.
const history = useHistory();
// Retrieve roles table columns
const columns = useRolesTableColumns();
// Roles table context.
const { roles, isRolesFetching, isRolesLoading } = useRolesContext();
// handles delete the given role.
const handleDeleteRole = ({ id, predefined }) => {
if (predefined) {
AppToaster.show({
message: intl.get('roles.error.you_cannot_delete_predefined_roles'),
intent: Intent.DANGER,
});
} else {
openAlert('role-delete', { roleId: id });
}
};
// Handles the edit of the given role.
const handleEditRole = ({ id, predefined }) => {
if (predefined) {
AppToaster.show({
message: intl.get('roles.error.you_cannot_edit_predefined_roles'),
intent: Intent.DANGER,
});
} else {
history.push(`/preferences/roles/${id}`);
}
};
return (
<RolesTable
columns={columns}
data={roles}
loading={isRolesLoading}
headerLoading={isRolesFetching}
progressBarLoading={isRolesFetching}
TableLoadingRenderer={TableSkeletonRows}
ContextMenu={ActionsMenu}
payload={{
onDeleteRole: handleDeleteRole,
onEditRole: handleEditRole,
}}
/>
);
}
const RolesTable = styled(DataTable)`
.table .tr {
min-height: 42px;
}
`;
export default compose(withAlertsActions)(RolesDataTable);

View File

@@ -0,0 +1,18 @@
// @ts-nocheck
import React from 'react';
import { RolesListProvider } from './RolesListProvider';
import RolesDataTable from './RolesDataTable';
/**
* Roles list.
*/
function RolesListPrefernces() {
return (
<RolesListProvider>
<RolesDataTable />
</RolesListProvider>
);
}
export default RolesListPrefernces;

View File

@@ -0,0 +1,40 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
import { useRoles } from '@/hooks/query';
const RolesListContext = React.createContext();
/**
* Roles list provider.
*/
function RolesListProvider({ ...props }) {
// Fetch roles list.
const {
data: roles,
isFetching: isRolesFetching,
isLoading: isRolesLoading,
} = useRoles();
// Provider state.
const provider = {
roles,
isRolesFetching,
isRolesLoading,
};
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_USERS,
)}
>
<RolesListContext.Provider value={provider} {...props} />
</div>
);
}
const useRolesContext = () => React.useContext(RolesListContext);
export { RolesListProvider, useRolesContext };

View File

@@ -0,0 +1,60 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import { safeCallback } from '@/utils';
import { Icon } from '@/components';
/**
* Context menu of roles.
*/
export function ActionsMenu({
payload: { onDeleteRole, onEditRole },
row: { original },
}) {
return (
<Menu>
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('roles.edit_roles')}
onClick={safeCallback(onEditRole, original)}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('roles.delete_roles')}
onClick={safeCallback(onDeleteRole, original)}
intent={Intent.DANGER}
/>
</Menu>
);
}
/**
* Retrieve Roles table columns.
* @returns
*/
export function useRolesTableColumns() {
return React.useMemo(
() => [
{
id: 'name',
Header: intl.get('roles.column.name'),
accessor: 'name',
className: 'name',
width: '80',
textOverview: true,
},
{
id: 'description',
Header: intl.get('roles.column.description'),
accessor: 'description',
className: 'description',
width: '180',
textOverview: true,
},
],
[],
);
}

View File

@@ -0,0 +1,32 @@
// @ts-nocheck
import intl from 'react-intl-universal';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from '@/components';
// handle delete errors.
export const handleDeleteErrors = (errors) => {
if (errors.find((error) => error.type === 'ROLE_PREFINED')) {
AppToaster.show({
message: intl.get('roles.error.role_is_predefined'),
intent: Intent.DANGER,
});
}
if (errors.find((error) => error.type === 'INVALIDATE_PERMISSIONS')) {
AppToaster.show({
message: intl.get('roles.error.the_submit_role_has_invalid_permissions'),
intent: Intent.DANGER,
});
}
if (
errors.find(
(error) => error.type === 'CANNOT_DELETE_ROLE_ASSOCIATED_TO_USERS',
)
) {
AppToaster.show({
message: intl.get(
'roles.error.you_cannot_delete_role_that_associated_to_users',
),
intent: Intent.DANGER,
});
}
};

View File

@@ -0,0 +1,53 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import styled from 'styled-components';
import { Tabs, Tab } from '@blueprintjs/core';
import '@/style/pages/Preferences/Users.scss';
import { Card } from '@/components';
import { CLASSES } from '@/constants/classes';
import PreferencesSubContent from '@/components/Preferences/PreferencesSubContent';
import withUserPreferences from '@/containers/Preferences/Users/withUserPreferences';
/**
* Preferences page - Users page.
*/
function UsersPreferences({ openDialog }) {
const onChangeTabs = (currentTabId) => {};
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_USERS,
)}
>
<UsersPereferencesCard>
<div className={classNames(CLASSES.PREFERENCES_PAGE_TABS)}>
<Tabs animate={true} onChange={onChangeTabs}>
<Tab
id="users"
title={intl.get('users')}
panel={<PreferencesSubContent preferenceTab="users" />}
/>
<Tab
id="roles"
title={intl.get('roles')}
panel={<PreferencesSubContent preferenceTab="roles" />}
/>
</Tabs>
</div>
</UsersPereferencesCard>
</div>
);
}
export default withUserPreferences(UsersPreferences);
const UsersPereferencesCard = styled(Card)`
padding: 0;
`;

View File

@@ -0,0 +1,41 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router-dom';
import { Button, Intent } from '@blueprintjs/core';
import { Icon, FormattedMessage as T } from '@/components';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { compose } from '@/utils';
function UsersActions({ openDialog, closeDialog }) {
const history = useHistory();
const onClickNewUser = () => {
openDialog('invite-user');
};
const onClickNewRole = () => {
history.push('/preferences/roles');
};
return (
<div className="preferences-actions">
<Button
icon={<Icon icon="plus" iconSize={12} />}
onClick={onClickNewUser}
intent={Intent.PRIMARY}
>
<T id={'invite_user'} />
</Button>
<Button
icon={<Icon icon="plus" iconSize={12} />}
onClick={onClickNewRole}
>
<T id={'new_role'} />
</Button>
</div>
);
}
export default compose(withDialogActions)(UsersActions);

View File

@@ -0,0 +1,18 @@
// @ts-nocheck
import React from 'react';
const UserDeleteAlert = React.lazy(
() => import('@/containers/Alerts/Users/UserDeleteAlert'),
);
const UserActivateAlert = React.lazy(
() => import('@/containers/Alerts/Users/UserActivateAlert'),
);
const UserInactivateAlert = React.lazy(
() => import('@/containers/Alerts/Users/UserInactivateAlert'),
);
export default [
{ name: 'user-delete', component: UserDeleteAlert },
{ name: 'user-activate', component: UserActivateAlert },
{ name: 'user-inactivate', component: UserInactivateAlert },
];

View File

@@ -0,0 +1,107 @@
// @ts-nocheck
import React, { useCallback } from 'react';
import { compose } from '@/utils';
import { DataTable, TableSkeletonRows, AppToaster } from '@/components';
import { useResendInvitation } from '@/hooks/query';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withAlertActions from '@/containers/Alert/withAlertActions';
import { ActionsMenu, useUsersListColumns } from './components';
import { useUsersListContext } from './UsersProvider';
import { Intent } from '@blueprintjs/core';
/**
* Users datatable.
*/
function UsersDataTable({
// #withDialogActions
openDialog,
// #withAlertActions
openAlert,
}) {
// Handle edit user action.
const handleEditUserAction = useCallback(
(user) => {
openDialog('user-form', { action: 'edit', userId: user.id });
},
[openDialog],
);
// Handle inactivate user action.
const handleInactivateUser = useCallback(
(user) => {
openAlert('user-inactivate', { userId: user.id });
},
[openAlert],
);
// Handle activate user action.
const handleActivateuser = useCallback(
(user) => {
openAlert('user-activate', { userId: user.id });
},
[openAlert],
);
// Handle delete user action.
const handleDeleteUser = useCallback(
(user) => {
openAlert('user-delete', { userId: user.id });
},
[openAlert],
);
const { mutateAsync: resendInviation } = useResendInvitation();
const handleResendInvitation = useCallback((user) => {
resendInviation(user.id)
.then(() => {
AppToaster.show({
message: 'User invitation has been re-sent to the user.',
intent: Intent.SUCCESS,
});
})
.catch(
({
response: {
data: { errors },
},
}) => {
if (errors.find((e) => e.type === 'USER_RECENTLY_INVITED')) {
AppToaster.show({
message:
'This person was recently invited. No need to invite them again just yet.',
intent: Intent.DANGER,
});
}
},
);
});
// Users list columns.
const columns = useUsersListColumns();
// Users list context.
const { users, isUsersLoading, isUsersFetching } = useUsersListContext();
return (
<DataTable
columns={columns}
data={users}
loading={isUsersLoading}
headerLoading={isUsersLoading}
progressBarLoading={isUsersFetching}
TableLoadingRenderer={TableSkeletonRows}
noInitialFetch={true}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditUserAction,
onActivate: handleActivateuser,
onInactivate: handleInactivateUser,
onDelete: handleDeleteUser,
onResendInvitation: handleResendInvitation,
}}
/>
);
}
export default compose(withDialogActions, withAlertActions)(UsersDataTable);

View File

@@ -0,0 +1,29 @@
// @ts-nocheck
import React, { useEffect } from 'react';
import intl from 'react-intl-universal';
import { UsersListProvider } from './UsersProvider';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import UsersDataTable from './UsersDataTable';
import { compose } from '@/utils';
/**
* Users list.
*/
function UsersListPreferences({
// #withDashboardActions
changePreferencesPageTitle,
}) {
useEffect(() => {
changePreferencesPageTitle(intl.get('users'));
}, [changePreferencesPageTitle]);
return (
<UsersListProvider>
<UsersDataTable />
</UsersListProvider>
);
}
export default compose(withDashboardActions)(UsersListPreferences);

View File

@@ -0,0 +1,26 @@
// @ts-nocheck
import React, { createContext } from 'react';
import { useUsers } from '@/hooks/query';
const UsersListContext = createContext();
/**
* Users list provider.
*/
function UsersListProvider(props) {
const { data: users, isLoading, isFetching } = useUsers();
const state = {
isUsersLoading: isLoading,
isUsersFetching: isFetching,
users,
};
return (
<UsersListContext.Provider value={state} {...props} />
);
}
const useUsersListContext = () => React.useContext(UsersListContext);
export { UsersListProvider, useUsersListContext };

View File

@@ -0,0 +1,162 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { FormattedMessage as T, Icon, If } from '@/components';
import {
Intent,
Button,
Popover,
Menu,
MenuDivider,
Tag,
MenuItem,
Position,
} from '@blueprintjs/core';
import { safeCallback, firstLettersArgs } from '@/utils';
/**
* Avatar cell.
*/
function AvatarCell(row) {
return <span className={'avatar'}>{firstLettersArgs(row.email)}</span>;
}
/**
* Users table actions menu.
*/
export function ActionsMenu({
row: { original },
payload: { onEdit, onInactivate, onActivate, onDelete, onResendInvitation },
}) {
return (
<Menu>
<If condition={original.invite_accepted_at}>
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('edit_user')}
onClick={safeCallback(onEdit, original)}
/>
<MenuDivider />
{original.active ? (
<MenuItem
text={intl.get('inactivate_user')}
onClick={safeCallback(onInactivate, original)}
icon={<Icon icon="pause-16" iconSize={16} />}
/>
) : (
<MenuItem
text={intl.get('activate_user')}
onClick={safeCallback(onActivate, original)}
icon={<Icon icon="play-16" iconSize={16} />}
/>
)}
</If>
<If condition={!original.invite_accepted_at}>
<MenuItem
text={'Resend invitation'}
onClick={safeCallback(onResendInvitation, original)}
icon={<Icon icon="send" iconSize={16} />}
/>
</If>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_user')}
onClick={safeCallback(onDelete, original)}
intent={Intent.DANGER}
/>
</Menu>
);
}
/**
* Status accessor.
*/
function StatusAccessor(user) {
return !user.is_invite_accepted ? (
<Tag minimal={true}>
<T id={'inviting'} />
</Tag>
) : user.active ? (
<Tag intent={Intent.SUCCESS} minimal={true}>
<T id={'activate'} />
</Tag>
) : (
<Tag intent={Intent.WARNING} minimal={true}>
<T id={'inactivate'} />
</Tag>
);
}
/**
* Actions cell.
*/
function ActionsCell(props) {
return (
<Popover
content={<ActionsMenu {...props} />}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
);
}
function FullNameAccessor(user) {
return user.is_invite_accepted ? user.full_name : user.email;
}
export const useUsersListColumns = () => {
return React.useMemo(
() => [
{
id: 'avatar',
Header: '',
accessor: AvatarCell,
width: 40,
},
{
id: 'full_name',
Header: intl.get('full_name'),
accessor: FullNameAccessor,
width: 150,
},
{
id: 'email',
Header: intl.get('email'),
accessor: 'email',
width: 150,
},
{
id: 'role_name',
Header: intl.get('users.column.role_name'),
accessor: 'role.name',
width: 120,
},
// {
// id: 'phone_number',
// Header: intl.get('phone_number'),
// accessor: 'phone_number',
// width: 120,
// },
{
id: 'status',
Header: intl.get('status'),
accessor: StatusAccessor,
width: 80,
className: 'status',
},
{
id: 'actions',
Header: '',
Cell: ActionsCell,
className: 'actions',
width: 50,
disableResizing: true,
},
],
[],
);
};

View File

@@ -0,0 +1,14 @@
// @ts-nocheck
import { connect } from 'react-redux';
import t from '@/store/types';
export const mapStateToProps = (state, props) => {};
export const mapDispatchToProps = (dispatch) => ({
openDialog: (name, payload) =>
dispatch({ type: t.OPEN_DIALOG, name, payload }),
closeDialog: (name, payload) =>
dispatch({ type: t.CLOSE_DIALOG, name, payload }),
});
export default connect(null, mapDispatchToProps);