diff --git a/client/src/actions/entry/modal.js b/client/src/actions/entry/modal.js
index 1cfff668..533bddc8 100755
--- a/client/src/actions/entry/modal.js
+++ b/client/src/actions/entry/modal.js
@@ -8,6 +8,13 @@ export const openUsersModal = () => ({
},
});
+export const openUserSettingsModal = () => ({
+ type: EntryActionTypes.MODAL_OPEN,
+ payload: {
+ type: ModalTypes.USER_SETTINGS,
+ },
+});
+
export const openAddProjectModal = () => ({
type: EntryActionTypes.MODAL_OPEN,
payload: {
diff --git a/client/src/components/App.jsx b/client/src/components/App.jsx
index 46246488..9a815282 100755
--- a/client/src/components/App.jsx
+++ b/client/src/components/App.jsx
@@ -4,19 +4,22 @@ import PropTypes from 'prop-types';
import HeaderContainer from '../containers/HeaderContainer';
import ProjectsContainer from '../containers/ProjectsContainer';
import UsersModalContainer from '../containers/UsersModalContainer';
+import UserSettingsModalContainer from '../containers/UserSettingsModalContainer';
import AddProjectModalContainer from '../containers/AddProjectModalContainer';
-const App = ({ isUsersModalOpened, isAddProjectModalOpened }) => (
+const App = ({ isUsersModalOpened, isUserSettingsModalOpened, isAddProjectModalOpened }) => (
<>
{isUsersModalOpened && }
+ {isUserSettingsModalOpened && }
{isAddProjectModalOpened && }
>
);
App.propTypes = {
isUsersModalOpened: PropTypes.bool.isRequired,
+ isUserSettingsModalOpened: PropTypes.bool.isRequired,
isAddProjectModalOpened: PropTypes.bool.isRequired,
};
diff --git a/client/src/components/Header/Header.jsx b/client/src/components/Header/Header.jsx
index 7f18bcb6..9c5ea2bd 100755
--- a/client/src/components/Header/Header.jsx
+++ b/client/src/components/Header/Header.jsx
@@ -16,14 +16,7 @@ const Header = React.memo(
isEditable,
onUsers,
onNotificationDelete,
- onUserUpdate,
- onUserAvatarUpload,
- onUserUsernameUpdate,
- onUserUsernameUpdateMessageDismiss,
- onUserEmailUpdate,
- onUserEmailUpdateMessageDismiss,
- onUserPasswordUpdate,
- onUserPasswordUpdateMessageDismiss,
+ onUserSettings,
onLogout,
}) => (
@@ -45,25 +38,7 @@ const Header = React.memo(
)}
-
+
{user.name}
@@ -80,14 +55,7 @@ Header.propTypes = {
isEditable: PropTypes.bool.isRequired,
onUsers: PropTypes.func.isRequired,
onNotificationDelete: PropTypes.func.isRequired,
- onUserUpdate: PropTypes.func.isRequired,
- onUserAvatarUpload: PropTypes.func.isRequired,
- onUserUsernameUpdate: PropTypes.func.isRequired,
- onUserUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
- onUserEmailUpdate: PropTypes.func.isRequired,
- onUserEmailUpdateMessageDismiss: PropTypes.func.isRequired,
- onUserPasswordUpdate: PropTypes.func.isRequired,
- onUserPasswordUpdateMessageDismiss: PropTypes.func.isRequired,
+ onUserSettings: PropTypes.func.isRequired,
onLogout: PropTypes.func.isRequired,
};
diff --git a/client/src/components/User/User.jsx b/client/src/components/User/User.jsx
index 1ad33360..1bf1c5e3 100755
--- a/client/src/components/User/User.jsx
+++ b/client/src/components/User/User.jsx
@@ -10,6 +10,7 @@ const SIZES = {
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large',
+ MASSIVE: 'massive',
};
// TODO: move to styles
@@ -43,6 +44,13 @@ const STYLES = {
padding: '12px 0 10px',
width: '36px',
},
+ massive: {
+ fontSize: '36px',
+ fontWeight: '500',
+ height: '100px',
+ padding: '32px 0 10px',
+ width: '100px',
+ },
};
const COLORS = [
diff --git a/client/src/components/UserPopup/EditAvatarStep.jsx b/client/src/components/UserPopup/EditAvatarStep.jsx
deleted file mode 100755
index 0282b988..00000000
--- a/client/src/components/UserPopup/EditAvatarStep.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import React, { useCallback, useEffect, useRef } from 'react';
-import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
-import { Button } from 'semantic-ui-react';
-import { Popup } from '../../lib/custom-ui';
-
-import User from '../User';
-
-import styles from './EditAvatarStep.module.css';
-
-const EditAvatarStep = React.memo(
- ({ defaultValue, name, isUploading, onUpload, onClear, onBack }) => {
- const [t] = useTranslation();
-
- const field = useRef(null);
-
- const handleFieldChange = useCallback(
- ({ target }) => {
- if (target.files[0]) {
- onUpload(target.files[0]);
-
- target.value = null; // eslint-disable-line no-param-reassign
- }
- },
- [onUpload],
- );
-
- useEffect(() => {
- field.current.focus();
- }, []);
-
- return (
- <>
-
- {t('common.editAvatar', {
- context: 'title',
- })}
-
-
-
-
-
-
-
- {defaultValue && }
-
- >
- );
- },
-);
-
-EditAvatarStep.propTypes = {
- defaultValue: PropTypes.string,
- name: PropTypes.string.isRequired,
- isUploading: PropTypes.bool.isRequired,
- onUpload: PropTypes.func.isRequired,
- onClear: PropTypes.func.isRequired,
- onBack: PropTypes.func.isRequired,
-};
-
-EditAvatarStep.defaultProps = {
- defaultValue: undefined,
-};
-
-export default EditAvatarStep;
diff --git a/client/src/components/UserPopup/EditNameStep.jsx b/client/src/components/UserPopup/EditNameStep.jsx
deleted file mode 100755
index 0e66b47a..00000000
--- a/client/src/components/UserPopup/EditNameStep.jsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import React, { useCallback, useEffect, useRef } from 'react';
-import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
-import { Button, Form } from 'semantic-ui-react';
-import { Input, Popup } from '../../lib/custom-ui';
-
-import { useField } from '../../hooks';
-
-import styles from './EditNameStep.module.css';
-
-const EditNameStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) => {
- const [t] = useTranslation();
- const [value, handleFieldChange] = useField(defaultValue);
-
- const field = useRef(null);
-
- const handleSubmit = useCallback(() => {
- const cleanValue = value.trim();
-
- if (!cleanValue) {
- field.current.select();
- return;
- }
-
- if (cleanValue !== defaultValue) {
- onUpdate(cleanValue);
- }
-
- onClose();
- }, [defaultValue, onUpdate, onClose, value]);
-
- useEffect(() => {
- field.current.select();
- }, []);
-
- return (
- <>
-
- {t('common.editName', {
- context: 'title',
- })}
-
-
-
-
- >
- );
-});
-
-EditNameStep.propTypes = {
- defaultValue: PropTypes.string.isRequired,
- onUpdate: PropTypes.func.isRequired,
- onBack: PropTypes.func.isRequired,
- onClose: PropTypes.func.isRequired,
-};
-
-export default EditNameStep;
diff --git a/client/src/components/UserPopup/UserPopup.jsx b/client/src/components/UserPopup/UserPopup.jsx
index bd624355..0a8cf2de 100755
--- a/client/src/components/UserPopup/UserPopup.jsx
+++ b/client/src/components/UserPopup/UserPopup.jsx
@@ -5,218 +5,45 @@ import { Menu } from 'semantic-ui-react';
import { withPopup } from '../../lib/popup';
import { Popup } from '../../lib/custom-ui';
-import { useSteps } from '../../hooks';
-import EditNameStep from './EditNameStep';
-import EditUsernameStep from './EditUsernameStep';
-import EditAvatarStep from './EditAvatarStep';
-import EditEmailStep from './EditEmailStep';
-import EditPasswordStep from './EditPasswordStep';
-
import styles from './UserPopup.module.css';
-const StepTypes = {
- EDIT_NAME: 'EDIT_NAME',
- EDIT_USERNAME: 'EDIT_USERNAME',
- EDIT_AVATAR: 'EDIT_AVATAR',
- EDIT_EMAIL: 'EDIT_EMAIL',
- EDIT_PASSWORD: 'EDIT_PASSWORD',
-};
+const UserStep = React.memo(({ onSettings, onLogout, onClose }) => {
+ const [t] = useTranslation();
-const UserStep = React.memo(
- ({
- email,
- name,
- username,
- avatar,
- isAvatarUploading,
- usernameUpdateForm,
- emailUpdateForm,
- passwordUpdateForm,
- onUpdate,
- onAvatarUpload,
- onUsernameUpdate,
- onUsernameUpdateMessageDismiss,
- onEmailUpdate,
- onEmailUpdateMessageDismiss,
- onPasswordUpdate,
- onPasswordUpdateMessageDismiss,
- onLogout,
- onClose,
- }) => {
- const [t] = useTranslation();
- const [step, openStep, handleBack] = useSteps();
+ const handleSettingsClick = useCallback(() => {
+ onSettings();
+ onClose();
+ }, [onSettings, onClose]);
- const handleNameEditClick = useCallback(() => {
- openStep(StepTypes.EDIT_NAME);
- }, [openStep]);
-
- const handleAvatarEditClick = useCallback(() => {
- openStep(StepTypes.EDIT_AVATAR);
- }, [openStep]);
-
- const handleUsernameEditClick = useCallback(() => {
- openStep(StepTypes.EDIT_USERNAME);
- }, [openStep]);
-
- const handleEmailEditClick = useCallback(() => {
- openStep(StepTypes.EDIT_EMAIL);
- }, [openStep]);
-
- const handlePasswordEditClick = useCallback(() => {
- openStep(StepTypes.EDIT_PASSWORD);
- }, [openStep]);
-
- const handleNameUpdate = useCallback(
- (newName) => {
- onUpdate({
- name: newName,
- });
- },
- [onUpdate],
- );
-
- const handleAvatarClear = useCallback(() => {
- onUpdate({
- avatar: null,
- });
- }, [onUpdate]);
-
- if (step) {
- switch (step.type) {
- case StepTypes.EDIT_NAME:
- return (
-
- );
- case StepTypes.EDIT_AVATAR:
- return (
-
- );
- case StepTypes.EDIT_USERNAME:
- return (
-
- );
- case StepTypes.EDIT_EMAIL:
- return (
-
- );
- case StepTypes.EDIT_PASSWORD:
- return (
-
- );
- default:
- }
- }
-
- return (
- <>
-
- {t('common.userActions', {
- context: 'title',
- })}
-
-
-
-
- >
- );
- },
-);
+ return (
+ <>
+
+ {t('common.userActions', {
+ context: 'title',
+ })}
+
+
+
+
+ >
+ );
+});
UserStep.propTypes = {
- email: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- username: PropTypes.string,
- avatar: PropTypes.string,
- isAvatarUploading: PropTypes.bool.isRequired,
- /* eslint-disable react/forbid-prop-types */
- usernameUpdateForm: PropTypes.object.isRequired,
- emailUpdateForm: PropTypes.object.isRequired,
- passwordUpdateForm: PropTypes.object.isRequired,
- /* eslint-enable react/forbid-prop-types */
- onUpdate: PropTypes.func.isRequired,
- onAvatarUpload: PropTypes.func.isRequired,
- onUsernameUpdate: PropTypes.func.isRequired,
- onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
- onEmailUpdate: PropTypes.func.isRequired,
- onEmailUpdateMessageDismiss: PropTypes.func.isRequired,
- onPasswordUpdate: PropTypes.func.isRequired,
- onPasswordUpdateMessageDismiss: PropTypes.func.isRequired,
+ onSettings: PropTypes.func.isRequired,
onLogout: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
-UserStep.defaultProps = {
- username: undefined,
- avatar: undefined,
-};
-
export default withPopup(UserStep);
diff --git a/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx b/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx
new file mode 100644
index 00000000..ba5026a0
--- /dev/null
+++ b/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx
@@ -0,0 +1,144 @@
+import React, { useCallback } from 'react';
+import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
+import { Button, Divider, Header, Tab } from 'semantic-ui-react';
+
+import EditInformation from './EditInformation';
+import EditAvatarPopup from './EditAvatarPopup';
+import EditUsernamePopup from './EditUsernamePopup';
+import EditEmailPopup from './EditEmailPopup';
+import EditPasswordPopup from './EditPasswordPopup';
+import User from '../../User';
+
+import styles from './AccountPane.module.css';
+
+const AccountPane = React.memo(
+ ({
+ email,
+ name,
+ username,
+ avatar,
+ isAvatarUploading,
+ usernameUpdateForm,
+ emailUpdateForm,
+ passwordUpdateForm,
+ onUpdate,
+ onAvatarUpload,
+ onUsernameUpdate,
+ onUsernameUpdateMessageDismiss,
+ onEmailUpdate,
+ onEmailUpdateMessageDismiss,
+ onPasswordUpdate,
+ onPasswordUpdateMessageDismiss,
+ }) => {
+ const [t] = useTranslation();
+
+ const handleAvatarDelete = useCallback(() => {
+ onUpdate({
+ avatar: null,
+ });
+ }, [onUpdate]);
+
+ return (
+
+
+
+
+
+
+
+
+
+ {t('common.authentication', {
+ context: 'title',
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ },
+);
+
+AccountPane.propTypes = {
+ email: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ username: PropTypes.string,
+ avatar: PropTypes.string,
+ isAvatarUploading: PropTypes.bool.isRequired,
+ /* eslint-disable react/forbid-prop-types */
+ usernameUpdateForm: PropTypes.object.isRequired,
+ emailUpdateForm: PropTypes.object.isRequired,
+ passwordUpdateForm: PropTypes.object.isRequired,
+ /* eslint-enable react/forbid-prop-types */
+ onUpdate: PropTypes.func.isRequired,
+ onAvatarUpload: PropTypes.func.isRequired,
+ onUsernameUpdate: PropTypes.func.isRequired,
+ onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
+ onEmailUpdate: PropTypes.func.isRequired,
+ onEmailUpdateMessageDismiss: PropTypes.func.isRequired,
+ onPasswordUpdate: PropTypes.func.isRequired,
+ onPasswordUpdateMessageDismiss: PropTypes.func.isRequired,
+};
+
+AccountPane.defaultProps = {
+ username: undefined,
+ avatar: undefined,
+};
+
+export default AccountPane;
diff --git a/client/src/components/UserSettingsModal/AccountPane/AccountPane.module.css b/client/src/components/UserSettingsModal/AccountPane/AccountPane.module.css
new file mode 100644
index 00000000..f2e68a67
--- /dev/null
+++ b/client/src/components/UserSettingsModal/AccountPane/AccountPane.module.css
@@ -0,0 +1,30 @@
+.action {
+ border: none;
+ display: inline-block;
+ height: 36px;
+ overflow: hidden;
+ position: relative;
+ transition: background 0.3s ease;
+ width: 100%;
+}
+
+.action:hover {
+ background: #e9e9e9 !important;
+}
+
+.actionButton {
+ background: transparent !important;
+ color: #6b808c !important;
+ font-weight: normal !important;
+ height: 36px;
+ line-height: 24px !important;
+ padding: 6px 12px !important;
+ text-align: left !important;
+ text-decoration: underline !important;
+ width: 100%;
+}
+
+.wrapper {
+ border: none !important;
+ box-shadow: none !important;
+}
diff --git a/client/src/components/UserSettingsModal/AccountPane/EditAvatarPopup.jsx b/client/src/components/UserSettingsModal/AccountPane/EditAvatarPopup.jsx
new file mode 100644
index 00000000..55182feb
--- /dev/null
+++ b/client/src/components/UserSettingsModal/AccountPane/EditAvatarPopup.jsx
@@ -0,0 +1,71 @@
+import React, { useCallback, useEffect, useRef } from 'react';
+import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
+import { Button } from 'semantic-ui-react';
+import { withPopup } from '../../../lib/popup';
+import { Popup } from '../../../lib/custom-ui';
+
+import styles from './EditAvatarPopup.module.css';
+
+const EditAvatarStep = React.memo(({ defaultValue, onUpload, onDelete, onClose }) => {
+ const [t] = useTranslation();
+
+ const field = useRef(null);
+
+ const handleFieldChange = useCallback(
+ ({ target }) => {
+ if (target.files[0]) {
+ onUpload(target.files[0]);
+ onClose();
+ }
+ },
+ [onUpload, onClose],
+ );
+
+ const handleDeleteClick = useCallback(() => {
+ onDelete();
+ onClose();
+ }, [onDelete, onClose]);
+
+ useEffect(() => {
+ field.current.focus();
+ }, []);
+
+ return (
+ <>
+
+ {t('common.editAvatar', {
+ context: 'title',
+ })}
+
+
+
+
+
+
+ {defaultValue && (
+
+ )}
+
+ >
+ );
+});
+
+EditAvatarStep.propTypes = {
+ defaultValue: PropTypes.string,
+ onUpload: PropTypes.func.isRequired,
+ onDelete: PropTypes.func.isRequired,
+ onClose: PropTypes.func.isRequired,
+};
+
+EditAvatarStep.defaultProps = {
+ defaultValue: undefined,
+};
+
+export default withPopup(EditAvatarStep);
diff --git a/client/src/components/UserPopup/EditAvatarStep.module.css b/client/src/components/UserSettingsModal/AccountPane/EditAvatarPopup.module.css
similarity index 92%
rename from client/src/components/UserPopup/EditAvatarStep.module.css
rename to client/src/components/UserSettingsModal/AccountPane/EditAvatarPopup.module.css
index 6b37db20..848fcb94 100644
--- a/client/src/components/UserPopup/EditAvatarStep.module.css
+++ b/client/src/components/UserSettingsModal/AccountPane/EditAvatarPopup.module.css
@@ -24,10 +24,9 @@
display: inline-block;
height: 36px;
overflow: hidden;
- margin-left: 8px;
position: relative;
transition: background 0.3s ease;
- width: calc(100% - 44px);
+ width: 100%;
}
.input:hover {
diff --git a/client/src/components/UserPopup/EditEmailStep.jsx b/client/src/components/UserSettingsModal/AccountPane/EditEmailPopup.jsx
similarity index 83%
rename from client/src/components/UserPopup/EditEmailStep.jsx
rename to client/src/components/UserSettingsModal/AccountPane/EditEmailPopup.jsx
index d15bbe60..19a97e3f 100644
--- a/client/src/components/UserPopup/EditEmailStep.jsx
+++ b/client/src/components/UserSettingsModal/AccountPane/EditEmailPopup.jsx
@@ -3,12 +3,13 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Form, Message } from 'semantic-ui-react';
-import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
-import { Input, Popup } from '../../lib/custom-ui';
+import { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';
+import { withPopup } from '../../../lib/popup';
+import { Input, Popup } from '../../../lib/custom-ui';
-import { useForm } from '../../hooks';
+import { useForm } from '../../../hooks';
-import styles from './EditNameStep.module.css';
+import styles from './EditEmailPopup.module.css';
const createMessage = (error) => {
if (!error) {
@@ -35,7 +36,7 @@ const createMessage = (error) => {
};
const EditEmailStep = React.memo(
- ({ defaultData, email, isSubmitting, error, onUpdate, onMessageDismiss, onBack, onClose }) => {
+ ({ defaultData, email, isSubmitting, error, onUpdate, onMessageDismiss, onClose }) => {
const [t] = useTranslation();
const wasSubmitting = usePrevious(isSubmitting);
@@ -109,7 +110,7 @@ const EditEmailStep = React.memo(
return (
<>
-
+
{t('common.editEmail', {
context: 'title',
})}
@@ -137,19 +138,15 @@ const EditEmailStep = React.memo(
className={styles.field}
onChange={handleFieldChange}
/>
- {data.email.trim() !== email && (
- <>
- {t('common.currentPassword')}
-
- >
- )}
+ {t('common.currentPassword')}
+