WIP fix-general

This commit is contained in:
elforjani3
2020-05-03 23:08:47 +02:00
parent a807cf6bb8
commit b91718f350
13 changed files with 380 additions and 139 deletions

View File

@@ -3,10 +3,12 @@ import AccountFormDialog from 'containers/Dashboard/Dialogs/AccountFormDialog';
import UserFormDialog from 'containers/Dashboard/Dialogs/UserFormDialog';
import ItemCategoryDialog from 'containers/Dashboard/Dialogs/ItemCategoryDialog';
import CurrencyDialog from 'containers/Dashboard/Dialogs/CurrencyDialog';
import InviteUserDialog from 'containers/Dashboard/Dialogs/InviteUserDialog';
export default function DialogsContainer() {
return (
<React.Fragment>
<InviteUserDialog />
<CurrencyDialog />
<ItemCategoryDialog />
<AccountFormDialog />

View File

@@ -1,12 +1,6 @@
import {connect} from 'react-redux';
import {
submitUser,
editUser,
fetchUser,
} from 'store/users/users.actions';
import {
getUserDetails
} from 'store/users/users.reducer';
import { connect } from 'react-redux';
import { submitInvite, editUser, fetchUser } from 'store/users/users.actions';
import { getUserDetails } from 'store/users/users.reducer';
import { getDialogPayload } from 'store/dashboard/dashboard.reducer';
import t from 'store/types';
@@ -15,18 +9,22 @@ export const mapStateToProps = (state, props) => {
return {
name: 'user-form',
payload: {action: 'new', id: null},
userDetails: dialogPayload.action === 'edit'
? getUserDetails(state, dialogPayload.user.id) : {},
payload: { action: 'new', id: null },
userDetails:
dialogPayload.action === 'edit'
? getUserDetails(state, dialogPayload.user.id)
: {},
};
};
export const mapDispatchToProps = (dispatch) => ({
openDialog: (name, payload) => dispatch({ type: t.OPEN_DIALOG, name, payload }),
closeDialog: (name, payload) => dispatch({ type: t.CLOSE_DIALOG, name, payload }),
submitUser: (form) => dispatch(submitUser({ form })),
editUser: (id, form) => dispatch(editUser({ form, id })),
fetchUser: (id) => dispatch(fetchUser({ id })),
openDialog: (name, payload) =>
dispatch({ type: t.OPEN_DIALOG, name, payload }),
closeDialog: (name, payload) =>
dispatch({ type: t.CLOSE_DIALOG, name, payload }),
requestSubmitInvite: (form) => dispatch(submitInvite({ form })),
requestEditUser: (id, form) => dispatch(editUser({ form, id })),
requestFetchUser: (id) => dispatch(fetchUser({ id })),
});
export default connect(mapStateToProps, mapDispatchToProps);
export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -1,10 +1,32 @@
import { connect } from 'react-redux';
import { fetchUsers, fetchUser, deleteUser } from 'store/users/users.actions';
import {
fetchUsers,
fetchUser,
deleteUser,
inactiveUser,
editUser,
} from 'store/users/users.actions';
import t from 'store/types';
import { getUserDetails } from 'store/users/users.reducer';
import { getDialogPayload } from 'store/dashboard/dashboard.reducer';
export const mapStateToProps = (state, props) => ({
usersList: state.users.list.results,
});
export const mapStateToProps = (state, props) => {
const dialogPayload = getDialogPayload(state, 'userList-form');
return {
name: 'userList-form',
payload: { action: 'new', id: null },
userDetails:
dialogPayload.action === 'edit'
? getUserDetails(state, dialogPayload.user.id)
: {},
editUser:
dialogPayload && dialogPayload.action === 'edit'
? state.users.list.results[dialogPayload.user.id]
: {},
usersList: state.users.list.results,
};
};
export const mapDispatchToProps = (dispatch) => ({
openDialog: (name, payload) =>
@@ -12,9 +34,11 @@ export const mapDispatchToProps = (dispatch) => ({
closeDialog: (name, payload) =>
dispatch({ type: t.CLOSE_DIALOG, name, payload }),
fetchUsers: () => dispatch(fetchUsers({})),
fetchUser: (id) => dispatch(fetchUser({ id })),
deleteUser: (id) => dispatch(deleteUser({ id })),
requestFetchUsers: () => dispatch(fetchUsers({})),
requestFetchUser: (id) => dispatch(fetchUser({ id })),
requestDeleteUser: (id) => dispatch(deleteUser({ id })),
requestInactiveUser: (id) => dispatch(inactiveUser({ id })),
requestEditUser: (id, form) => dispatch(editUser({ form, id })),
});
export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -0,0 +1,192 @@
import React, { useMemo } from 'react';
import { useIntl } from 'react-intl';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import {
Dialog,
Button,
FormGroup,
InputGroup,
Intent,
Classes,
} from '@blueprintjs/core';
import UserListDialogConnect from 'connectors/UsersList.connector';
import DialogReduxConnect from 'components/DialogReduxConnect';
import useAsync from 'hooks/async';
import { objectKeysTransform } from 'utils';
import { pick, snakeCase } from 'lodash';
import ErrorMessage from 'components/ErrorMessage';
import classNames from 'classnames';
import AppToaster from 'components/AppToaster';
import { compose } from 'utils';
function InviteUserDialog({
name,
payload,
isOpen,
closeDialog,
requestFetchUser,
requestEditUser,
}) {
const intl = useIntl();
const fetchHook = useAsync(async () => {
await Promise.all([
...(payload.action === 'edit' ? [requestFetchUser(payload.user.id)] : []),
]);
}, false);
const validationSchema = Yup.object().shape({
first_name: Yup.string().required(intl.formatMessage({ id: 'required' })),
last_name: Yup.string().required(intl.formatMessage({ id: 'required' })),
email: Yup.string()
.email()
.required(intl.formatMessage({ id: 'required' })),
phone_number: Yup.number().required(intl.formatMessage({ id: 'required' })),
});
const initialValues = useMemo(
() => ({
first_name: '',
last_name: '',
email: '',
phone_number: '',
}),
[]
);
const formik = useFormik({
enableReinitialize: true,
initialValues: {
...(payload.action === 'edit' &&
pick(
objectKeysTransform(payload.user, snakeCase),
Object.keys(initialValues)
)),
},
validationSchema,
onSubmit: (values, { setSubmitting }) => {
const form = {
...values,
};
if (payload.action === 'edit') {
requestEditUser(payload.user.id, form)
.then((response) => {
closeDialog(name);
AppToaster.show({
message: 'the_user_details_has_been_updated',
});
setSubmitting(false);
})
.catch((error) => {
setSubmitting(false);
});
}
},
});
const { values, errors, touched } = useMemo(() => formik, [formik]);
const onDialogOpening = () => {
fetchHook.execute();
};
const onDialogClosed = () => {
formik.resetForm();
};
const handleClose = () => {
closeDialog(name);
};
return (
<Dialog
name={name}
title={payload.action === 'edit' ? 'Edit invite' : ''}
className={classNames({
'dialog--loading': fetchHook.pending,
'dialog--invite-user': true,
})}
autoFocus={true}
canEscapeKeyClose={true}
isOpen={isOpen}
isLoading={fetchHook.pending}
onClosed={onDialogClosed}
onOpening={onDialogOpening}
>
<form onSubmit={formik.handleSubmit}>
<div className={Classes.DIALOG_BODY}>
<FormGroup
label={'First Name'}
className={'form-group--first-name'}
intent={errors.first_name && touched.first_name && Intent.DANGER}
helperText={<ErrorMessage name='first_name' {...formik} />}
inline={true}
>
<InputGroup
intent={errors.first_name && touched.first_name && Intent.DANGER}
{...formik.getFieldProps('first_name')}
/>
</FormGroup>
<FormGroup
label={'Last Name'}
className={'form-group--last-name'}
intent={errors.last_name && touched.last_name && Intent.DANGER}
helperText={<ErrorMessage name='last_name' {...formik} />}
inline={true}
>
<InputGroup
intent={errors.last_name && touched.last_name && Intent.DANGER}
{...formik.getFieldProps('last_name')}
/>
</FormGroup>
<FormGroup
label={'Email'}
className={'form-group--email'}
intent={errors.email && touched.email && Intent.DANGER}
helperText={<ErrorMessage name='email' {...formik} />}
inline={true}
>
<InputGroup
medium={true}
intent={errors.email && touched.email && Intent.DANGER}
{...formik.getFieldProps('email')}
/>
</FormGroup>
<FormGroup
label={'Phone Number'}
className={'form-group--phone-number'}
intent={
errors.phone_number && touched.phone_number && Intent.DANGER
}
helperText={<ErrorMessage name='phone_number' {...formik} />}
inline={true}
>
<InputGroup
intent={
errors.phone_number && touched.phone_number && Intent.DANGER
}
{...formik.getFieldProps('phone_number')}
/>
</FormGroup>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleClose}>Close</Button>
<Button intent={Intent.PRIMARY} type='submit'>
{payload.action === 'edit' ? 'Edit' : ''}
</Button>
</div>
</div>
</form>
</Dialog>
);
}
export default compose(
UserListDialogConnect,
DialogReduxConnect
)(InviteUserDialog);

View File

@@ -21,9 +21,9 @@ import classNames from 'classnames';
import { compose } from 'utils';
function UserFormDialog({
fetchUser,
submitUser,
editUser,
requestFetchUser,
requestSubmitInvite,
requestEditUser,
name,
payload,
isOpen,
@@ -32,12 +32,12 @@ function UserFormDialog({
const intl = useIntl();
const fetchHook = useAsync(async () => {
await Promise.all([
...(payload.action === 'edit' ? [fetchUser(payload.user.id)] : []),
...(payload.action === 'edit' ? [requestFetchUser(payload.user.id)] : []),
]);
}, false);
const validationSchema = Yup.object().shape({
email: Yup.string().email().required(),
email: Yup.string().email().required(intl.formatMessage({id:'required'})),
});
const initialValues = {
@@ -47,7 +47,6 @@ function UserFormDialog({
objectKeysTransform(payload.user, snakeCase),
Object.keys(validationSchema.fields)
)),
password: '',
};
const formik = useFormik({
@@ -57,17 +56,16 @@ function UserFormDialog({
onSubmit: (values) => {
const form = {
...values,
confirm_password: values.password,
};
if (payload.action === 'edit') {
editUser(payload.user.id, form).then((response) => {
requestEditUser(payload.user.id, form).then((response) => {
AppToaster.show({
message: 'the_user_details_has_been_updated',
});
closeDialog(name);
});
} else {
submitUser(form).then((response) => {
requestSubmitInvite(form).then((response) => {
AppToaster.show({
message: 'the_user_has_been_invited',
});
@@ -93,7 +91,7 @@ function UserFormDialog({
return (
<Dialog
name={name}
title={payload.action === 'edit' ? 'Edit invite' : 'New invite'}
title={payload.action === 'edit' ? 'Edit invite' : 'invite User'}
className={classNames({
'dialog--loading': fetchHook.pending,
'dialog--invite-form': true,
@@ -120,19 +118,6 @@ function UserFormDialog({
{...formik.getFieldProps('email')}
/>
</FormGroup>
<FormGroup
label={'Email'}
className={'form-group--email'}
intent={errors.email && touched.email && Intent.DANGER}
helperText={<ErrorMessage name='email' {...formik} />}
inline={true}
>
<InputGroup
medium={true}
intent={errors.email && touched.email && Intent.DANGER}
{...formik.getFieldProps('email')}
/>
</FormGroup>
</div>
<div className={Classes.DIALOG_FOOTER}>

View File

@@ -1,49 +1,36 @@
import React, {useCallback} from 'react';
import {
Tabs,
Tab,
Button,
Intent,
} from '@blueprintjs/core';
import React, { useCallback } from 'react';
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
import PreferencesSubContent from 'components/Preferences/PreferencesSubContent';
import connector from 'connectors/UsersPreferences.connector';
function UsersPreferences({
openDialog,
}) {
const onChangeTabs = (currentTabId) => {
};
function UsersPreferences({ openDialog }) {
const onChangeTabs = (currentTabId) => {};
const onClickNewUser = useCallback(() => {
openDialog('user-form');
}, [openDialog]);
return (
<div class="preferences__inside-content preferences__inside-content--users-roles">
<div class="preferences__tabs">
<Tabs
animate={true}
large={true}
onChange={onChangeTabs}>
<Tab id="users" title="Users" />
<Tab id="roles" title="Roles" />
<div class='preferences__inside-content preferences__inside-content--users-roles'>
<div class='preferences__tabs'>
<Tabs animate={true} large={true} onChange={onChangeTabs}>
<Tab id='users' title='Users' />
<Tab id='roles' title='Roles' />
<div class="preferences__tabs-extra-actions">
<Button
intent={Intent.PRIMARY}
onClick={onClickNewUser}>New invite</Button>
<div class='preferences__tabs-extra-actions'>
<Button intent={Intent.PRIMARY} onClick={onClickNewUser}>
Invite User
</Button>
<Button
intent={Intent.PRIMARY}
onClick={onClickNewUser}>New Role</Button>
</div>
<Button intent={Intent.PRIMARY} onClick={onClickNewUser}>
New Role
</Button>
</div>
</Tabs>
</div>
<PreferencesSubContent preferenceTab="users" />
<PreferencesSubContent preferenceTab='users' />
</div>
);
}
export default connector(UsersPreferences);
export default connector(UsersPreferences);

View File

@@ -21,30 +21,48 @@ import DialogConnect from 'connectors/Dialog.connector';
import DashboardConnect from 'connectors/Dashboard.connector';
function UsersListPreferences({
fetchUsers,
requestFetchUsers,
usersList,
openDialog,
closeDialog,
deleteUser,
requestDeleteUser,
requestInactiveUser,
onFetchData,
}) {
const [deleteUserState, setDeleteUserState] = useState(false);
const [inactiveUserState, setInactiveUserState] = useState(false);
const asyncHook = useAsync(async () => {
await Promise.all([fetchUsers()]);
await Promise.all([requestFetchUsers()]);
}, []);
const onInactiveUser = (user) => {};
const onInactiveUser = (user) => {
setInactiveUserState(user);
};
// Handle cancel inactive user alert
const handleCancelInactiveUser = useCallback(() => {
setInactiveUserState(false);
}, []);
// handel confirm user activation
const handleConfirmUserActive = useCallback(() => {
requestInactiveUser(inactiveUserState.id).then(() => {
setInactiveUserState(false);
requestFetchUsers();
AppToaster.show({ message: 'the_user_has_been_inactivated' });
});
}, [inactiveUserState, requestInactiveUser, requestFetchUsers]);
const onDeleteUser = (user) => {
setDeleteUserState(user);
};
const handleCancelUserDelete = () => {
setDeleteUserState(false);
};
const onEditUser = (user) => () => {
const onEditInviteUser = (user) => () => {
const form = Object.keys(user).reduce((obj, key) => {
const camelKey = snakeCase(key);
obj[camelKey] = user[key];
@@ -54,12 +72,22 @@ function UsersListPreferences({
openDialog('user-form', { action: 'edit', user: form });
};
const onEditUser = (user) => () => {
const form = Object.keys(user).reduce((obj, key) => {
const camelKey = snakeCase(key);
obj[camelKey] = user[key];
return obj;
}, {});
openDialog('userList-form', { action: 'edit', user: form });
};
const handleConfirmUserDelete = () => {
if (!deleteUserState) {
return;
}
deleteUser(deleteUserState.id).then((response) => {
requestDeleteUser(deleteUserState.id).then((response) => {
setDeleteUserState(false);
AppToaster.show({
message: 'the_user_has_been_deleted',
@@ -67,16 +95,18 @@ function UsersListPreferences({
});
};
const actionMenuList = (user) => (
<Menu>
<MenuItem text='Edit User' onClick={onEditUser(user)} />
<MenuItem text='New Account' />
<MenuDivider />
<MenuItem text='Inactivate User' onClick={() => onInactiveUser(user)} />
<MenuItem text='Delete User' onClick={() => onDeleteUser(user)} />
</Menu>
const actionMenuList = useCallback(
(user) => (
<Menu>
<MenuItem text='Edit User' onClick={onEditUser(user)} />
<MenuDivider />
<MenuItem text='Edit Invite ' onClick={onEditInviteUser(user)} />
<MenuItem text='Inactivate User' onClick={() => onInactiveUser(user)} />
<MenuItem text='Delete User' onClick={() => onDeleteUser(user)} />
</Menu>
),
[]
);
console.log(usersList, 'X');
const columns = useMemo(
() => [
@@ -151,6 +181,21 @@ function UsersListPreferences({
able to restore it later, but it will become private to you.
</p>
</Alert>
<Alert
cancelButtonText='Cancel'
confirmButtonText='Inactivate'
icon='trash'
intent={Intent.WARNING}
isOpen={inactiveUserState}
onCancel={handleCancelInactiveUser}
onConfirm={handleConfirmUserActive}
>
<p>
Are you sure you want to move <b>filename</b> to Trash? You will be
able to restore it later, but it will become private to you.
</p>
</Alert>
</LoadingIndicator>
);
}

View File

@@ -1,36 +1,46 @@
import ApiService from "services/ApiService";
import ApiService from 'services/ApiService';
import t from 'store/types';
export const fetchUsers = () => {
return (dispatch) => new Promise((resolve, reject) => {
ApiService.get(`users`).then((response) => {
dispatch({
type: t.USERS_LIST_SET,
users: response.data.users,
});
resolve(response);
}).catch((error) => { reject(error); });
});
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.get(`users`)
.then((response) => {
dispatch({
type: t.USERS_LIST_SET,
users: response.data.users,
});
resolve(response);
})
.catch((error) => {
reject(error);
});
});
};
export const fetchUser = ({ id }) => {
return (dispatch) => new Promise((resolve, reject) => {
ApiService.get(`users/${id}`).then((response) => {
dispatch({
type: t.USER_DETAILS_SET,
user: response.data.user,
});
resolve(response);
}).catch(error => { reject(error); });
});
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.get(`users/${id}`)
.then((response) => {
dispatch({
type: t.USER_DETAILS_SET,
user: response.data.user,
});
resolve(response);
})
.catch((error) => {
reject(error);
});
});
};
export const deleteUser = ({ id }) => {
return (dispatch) => ApiService.delete(`users/${id}`);
};
export const submitUser = ({ form }) => {
return (dispatch) => ApiService.post(`users`, form);
export const submitInvite = ({ form }) => {
return (dispatch) => ApiService.post(`invite/send`, form);
};
export const editUser = ({ form, id }) => {
@@ -38,9 +48,9 @@ export const editUser = ({ form, id }) => {
};
export const inactiveUser = ({ id }) => {
return (dispatch) => ApiService.post(`users/${id}/inactive`);
return (dispatch) => ApiService.put(`users/${id}/inactive`);
};
export const activeUser = ({ id }) => {
return (dispatch) => ApiService.post(`users/${id}/active`);
}
return (dispatch) => ApiService.put(`users/${id}/active`);
};

View File

@@ -46,6 +46,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
@import 'pages/items';
@import 'pages/invite-form.scss';
@import "pages/currency";
@import "pages/invite-user.scss";
// Views
@import 'views/filter-dropdown';

View File

@@ -8,5 +8,8 @@
width: 250px;
}
}
.bp3-dialog-header {
height: 170px;
}
}
}

View File

@@ -1,6 +1,3 @@
// .dialog--invite-form {
// }
.dialog--invite-form {
&.bp3-dialog {
@@ -30,18 +27,3 @@
}
}
// .bp3-dialog-body {
// .bp3-form-group.bp3-inline {
// .bp3-label {
// min-width: 100px;
// }
// .bp3-form-content {
// width: 250px;
// }
// }
// }
// .bp3-dialog-footer-actions {
// display: flex;
// justify-content: flex-end;
// margin-right: 100px;
// }

View File

@@ -0,0 +1,12 @@
.dialog--invite-user {
.bp3-dialog-body {
.bp3-form-group.bp3-inline {
.bp3-label {
min-width: 140px;
}
.bp3-form-content {
width: 250px;
}
}
}
}

View File

@@ -22,7 +22,7 @@
}
}
&-menu {
width: 240px;
width: 374px;
}
&-button {
margin-right: 10px;
@@ -86,7 +86,7 @@
min-width: 140px;
}
.bp3-form-content {
width: 250px;
width: 384px;
}
}
}