Add email and password change functionality for a current user, remove deep compare hooks

This commit is contained in:
Maksim Eltyshev
2019-10-18 08:06:34 +05:00
parent e564729598
commit 2566ff376e
67 changed files with 1232 additions and 267 deletions

View File

@@ -5,7 +5,7 @@ import { Icon, Menu } from 'semantic-ui-react';
import Paths from '../../constants/Paths';
import NotificationsPopup from './NotificationsPopup';
import UserPopup from './UserPopup';
import UserPopup from '../UserPopup';
import styles from './Header.module.css';
@@ -14,10 +14,14 @@ const Header = React.memo(
user,
notifications,
isEditable,
onUsers,
onNotificationDelete,
onUserUpdate,
onUserAvatarUpload,
onNotificationDelete,
onUsers,
onUserEmailUpdate,
onUserEmailUpdateMessageDismiss,
onUserPasswordUpdate,
onUserPasswordUpdateMessageDismiss,
onLogout,
}) => (
<div className={styles.wrapper}>
@@ -40,11 +44,18 @@ const Header = React.memo(
</Menu.Item>
</NotificationsPopup>
<UserPopup
email={user.email}
name={user.name}
avatar={user.avatar}
isAvatarUploading={user.isAvatarUploading}
emailUpdateForm={user.emailUpdateForm}
passwordUpdateForm={user.passwordUpdateForm}
onUpdate={onUserUpdate}
onAvatarUpload={onUserAvatarUpload}
onEmailUpdate={onUserEmailUpdate}
onEmailUpdateMessageDismiss={onUserEmailUpdateMessageDismiss}
onPasswordUpdate={onUserPasswordUpdate}
onPasswordUpdateMessageDismiss={onUserPasswordUpdateMessageDismiss}
onLogout={onLogout}
>
<Menu.Item className={styles.item}>{user.name}</Menu.Item>
@@ -61,10 +72,14 @@ Header.propTypes = {
notifications: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
isEditable: PropTypes.bool.isRequired,
onUsers: PropTypes.func.isRequired,
onNotificationDelete: PropTypes.func.isRequired,
onUserUpdate: PropTypes.func.isRequired,
onUserAvatarUpload: PropTypes.func.isRequired,
onNotificationDelete: PropTypes.func.isRequired,
onUsers: PropTypes.func.isRequired,
onUserEmailUpdate: PropTypes.func.isRequired,
onUserEmailUpdateMessageDismiss: PropTypes.func.isRequired,
onUserPasswordUpdate: PropTypes.func.isRequired,
onUserPasswordUpdateMessageDismiss: PropTypes.func.isRequired,
onLogout: PropTypes.func.isRequired,
};

View File

@@ -1,74 +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 (
<>
<Popup.Header onBack={onBack}>
{t('common.editAvatar', {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<User name={name} avatar={defaultValue} size="large" />
<div className={styles.input}>
<Button content={t('action.uploadNewAvatar')} className={styles.customButton} />
<input
ref={field}
type="file"
accept="image/*"
disabled={isUploading}
className={styles.file}
onChange={handleFieldChange}
/>
</div>
{defaultValue && <Button negative content={t('action.deleteAvatar')} onClick={onClear} />}
</Popup.Content>
</>
);
},
);
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;

View File

@@ -1,35 +0,0 @@
.customButton {
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;
}
.file {
bottom: 0;
left: 0;
opacity: 0;
position: absolute;
right: 0;
top: 0;
z-index: 1;
}
.input {
border: none;
display: inline-block;
height: 36px;
overflow: hidden;
margin-left: 8px;
position: relative;
transition: background 0.3s ease;
width: calc(100% - 44px);
}
.input:hover {
background: #e9e9e9 !important;
}

View File

@@ -1,69 +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 (
<>
<Popup.Header onBack={onBack}>
{t('common.editName', {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<Form onSubmit={handleSubmit}>
<div className={styles.text}>{t('common.name')}</div>
<Input
fluid
ref={field}
value={value}
className={styles.field}
onChange={handleFieldChange}
/>
<Button positive content={t('action.save')} />
</Form>
</Popup.Content>
</>
);
});
EditNameStep.propTypes = {
defaultValue: PropTypes.string.isRequired,
onUpdate: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
export default EditNameStep;

View File

@@ -1,10 +0,0 @@
.field {
margin-bottom: 8px;
}
.text {
color: #444444;
font-size: 12px;
font-weight: bold;
padding-bottom: 6px;
}

View File

@@ -1,116 +0,0 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
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 EditAvatarStep from './EditAvatarStep';
import styles from './UserPopup.module.css';
const StepTypes = {
EDIT_NAME: 'EDIT_NAME',
EDIT_AVATAR: 'EDIT_AVATAR',
};
const UserStep = React.memo(
({
name, avatar, isAvatarUploading, onUpdate, onAvatarUpload, onLogout, onClose,
}) => {
const [t] = useTranslation();
const [step, openStep, handleBack] = useSteps();
const handleNameEditClick = useCallback(() => {
openStep(StepTypes.EDIT_NAME);
}, [openStep]);
const handleAvatarEditClick = useCallback(() => {
openStep(StepTypes.EDIT_AVATAR);
}, [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 (
<EditNameStep
defaultValue={name}
onUpdate={handleNameUpdate}
onBack={handleBack}
onClose={onClose}
/>
);
case StepTypes.EDIT_AVATAR:
return (
<EditAvatarStep
defaultValue={avatar}
name={name}
isUploading={isAvatarUploading}
onUpload={onAvatarUpload}
onClear={handleAvatarClear}
onBack={handleBack}
/>
);
default:
}
}
return (
<>
<Popup.Header>{name}</Popup.Header>
<Popup.Content>
<Menu secondary vertical className={styles.menu}>
<Menu.Item className={styles.menuItem} onClick={handleNameEditClick}>
{t('action.editName', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleAvatarEditClick}>
{t('action.editAvatar', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={onLogout}>
{t('action.logOut', {
context: 'title',
})}
</Menu.Item>
</Menu>
</Popup.Content>
</>
);
},
);
UserStep.propTypes = {
name: PropTypes.string.isRequired,
avatar: PropTypes.string,
isAvatarUploading: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired,
onAvatarUpload: PropTypes.func.isRequired,
onLogout: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
UserStep.defaultProps = {
avatar: undefined,
};
export default withPopup(UserStep);

View File

@@ -1,9 +0,0 @@
.menu {
margin: -7px -12px -5px !important;
width: calc(100% + 24px) !important;
}
.menuItem {
margin: 0 !important;
padding-left: 14px !important;
}

View File

@@ -1,3 +0,0 @@
import UserPopup from './UserPopup';
export default UserPopup;