chrone: sperate client and server to different repos.

This commit is contained in:
a.bouhuolia
2021-09-21 17:13:53 +02:00
parent e011b2a82b
commit 18df5530c7
10015 changed files with 17686 additions and 97524 deletions

View 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);

View 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);

View 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'} />
</>
);
}

View 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);

View 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);

View 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 };

View 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,
},
],
[],
);
};

View 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);