mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 22:00:31 +00:00
chrone: sperate client and server to different repos.
This commit is contained in:
0
src/containers/Preferences/Users/RolesList.js
Normal file
0
src/containers/Preferences/Users/RolesList.js
Normal file
39
src/containers/Preferences/Users/Users.js
Normal file
39
src/containers/Preferences/Users/Users.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { Tabs, Tab } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import 'style/pages/Preferences/Users.scss';
|
||||
|
||||
import { CLASSES } from 'common/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,
|
||||
)}
|
||||
>
|
||||
<div className={classNames(CLASSES.CARD)}>
|
||||
<div className={classNames(CLASSES.PREFERENCES_PAGE_TABS)}>
|
||||
<Tabs animate={true} onChange={onChangeTabs}>
|
||||
<Tab id="users" title={intl.get('users')} />
|
||||
<Tab id="roles" title={intl.get('roles')} />
|
||||
</Tabs>
|
||||
</div>
|
||||
<PreferencesSubContent preferenceTab="users" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withUserPreferences(UsersPreferences);
|
||||
40
src/containers/Preferences/Users/UsersActions.js
Normal file
40
src/containers/Preferences/Users/UsersActions.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import Icon from 'components/Icon';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import {compose} from 'utils';
|
||||
|
||||
function UsersActions({
|
||||
openDialog,
|
||||
closeDialog,
|
||||
}) {
|
||||
const onClickNewUser = () => {
|
||||
openDialog('invite-user');
|
||||
};
|
||||
|
||||
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={onClickNewUser}>
|
||||
<T id={'new_role'} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
)(UsersActions);
|
||||
14
src/containers/Preferences/Users/UsersAlerts.js
Normal file
14
src/containers/Preferences/Users/UsersAlerts.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import UserDeleteAlert from 'containers/Alerts/Users/UserDeleteAlert';
|
||||
import UserInactivateAlert from 'containers/Alerts/Users/UserInactivateAlert';
|
||||
import UserActivateAlert from 'containers/Alerts/Users/UserActivateAlert';
|
||||
|
||||
export default function UsersAlerts() {
|
||||
return (
|
||||
<>
|
||||
<UserDeleteAlert name={'user-delete'} />
|
||||
<UserInactivateAlert name={'user-inactivate'} />
|
||||
<UserActivateAlert name={'user-activate'} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
105
src/containers/Preferences/Users/UsersDataTable.js
Normal file
105
src/containers/Preferences/Users/UsersDataTable.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { DataTable } from 'components';
|
||||
import { useResendInvitation } from 'hooks/query';
|
||||
import AppToaster from 'components/AppToaster';
|
||||
|
||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||
|
||||
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);
|
||||
33
src/containers/Preferences/Users/UsersList.js
Normal file
33
src/containers/Preferences/Users/UsersList.js
Normal file
@@ -0,0 +1,33 @@
|
||||
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 UsersAlerts from './UsersAlerts';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Users list.
|
||||
*/
|
||||
function UsersListPreferences({
|
||||
// #withDashboardActions
|
||||
changePreferencesPageTitle,
|
||||
}) {
|
||||
|
||||
useEffect(() => {
|
||||
changePreferencesPageTitle(intl.get('users'));
|
||||
}, [changePreferencesPageTitle]);
|
||||
|
||||
return (
|
||||
<UsersListProvider>
|
||||
<UsersDataTable />
|
||||
<UsersAlerts />
|
||||
</UsersListProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
)(UsersListPreferences);
|
||||
25
src/containers/Preferences/Users/UsersProvider.js
Normal file
25
src/containers/Preferences/Users/UsersProvider.js
Normal file
@@ -0,0 +1,25 @@
|
||||
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 };
|
||||
160
src/containers/Preferences/Users/components.js
Normal file
160
src/containers/Preferences/Users/components.js
Normal file
@@ -0,0 +1,160 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import {
|
||||
Intent,
|
||||
Button,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuDivider,
|
||||
Tag,
|
||||
MenuItem,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { safeCallback, firstLettersArgs } from 'utils';
|
||||
import { Icon, If } from 'components';
|
||||
|
||||
/**
|
||||
* 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: 'phone_number',
|
||||
Header: intl.get('phone_number'),
|
||||
accessor: 'phone_number',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
Header: 'Status',
|
||||
accessor: StatusAccessor,
|
||||
width: 80,
|
||||
className: 'status',
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: '',
|
||||
Cell: ActionsCell,
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
disableResizing: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
13
src/containers/Preferences/Users/withUserPreferences.js
Normal file
13
src/containers/Preferences/Users/withUserPreferences.js
Normal file
@@ -0,0 +1,13 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user