changing user password and email; notification messages redesign; some fixes

This commit is contained in:
2020-10-23 10:10:21 +03:00
parent 516cdf5b82
commit 438868b58c
7 changed files with 177 additions and 42 deletions
+8
View File
@@ -13,3 +13,11 @@ export const login = (login: string, password: string, recaptcha: string) => pos
recaptcha recaptcha
}); });
export const userLogout = () => post<string>('auth/logout'); export const userLogout = () => post<string>('auth/logout');
export const changeEmail = (old_email: string, new_email: string) => post<string>('auth/changeEmail', {
old_email,
new_email
});
export const changePassword = (old_password: string, new_password: string) => post<string>('auth/changePassword', {
old_password,
new_password
});
+2
View File
@@ -6,6 +6,8 @@ const errors: { [key: number]: string } = {
6: 'Токен истек!', 6: 'Токен истек!',
8: 'Новый и старый пароли совпадают!', 8: 'Новый и старый пароли совпадают!',
9: 'Необходима проверка ReCaptcha', 9: 'Необходима проверка ReCaptcha',
10: 'Неверно указан старый E-Mail',
11: 'Неверно указан старый пароль',
100: 'Не найдено!' 100: 'Не найдено!'
}; };
-5
View File
@@ -85,11 +85,6 @@ a
margin: .5rem auto margin: .5rem auto
.Notifications .Notifications
position: fixed
bottom: 0
left: 0
z-index: 100000
@media (min-width: 500px) @media (min-width: 500px)
padding: 1rem padding: 1rem
box-sizing: border-box box-sizing: border-box
+8 -6
View File
@@ -43,11 +43,17 @@ class App extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
parser.registerTag('spoiler', SpoilerTag);
parser.registerTag('color', ColorTag);
}
componentDidMount() {
const { setUser, setSettings } = this.props;
const cachedTheme = localStorage.getItem('theme'); const cachedTheme = localStorage.getItem('theme');
if(cachedTheme) if(cachedTheme)
document.body.setAttribute('theme', cachedTheme); document.body.setAttribute('theme', cachedTheme);
const { setUser, setSettings } = props;
fetchCurrentUser().then(user => { fetchCurrentUser().then(user => {
setUser(user); setUser(user);
fetchUserSettings().then(settings => { fetchUserSettings().then(settings => {
@@ -62,9 +68,6 @@ class App extends React.Component<Props, State> {
console.error(err.text); console.error(err.text);
}); });
parser.registerTag('spoiler', SpoilerTag);
parser.registerTag('color', ColorTag);
getVersion().then(res => { getVersion().then(res => {
if(version !== res.version) { if(version !== res.version) {
console.debug(`Current back-end version: ${res.version}. Clearing cache`); console.debug(`Current back-end version: ${res.version}. Clearing cache`);
@@ -80,13 +83,12 @@ class App extends React.Component<Props, State> {
render() { render() {
const { modal } = this.props; const { modal } = this.props;
const notificationWidth = window.screen.width >= 500 ? 500 : window.screen.width;
return ( return (
<> <>
<Navbar/> <Navbar/>
<div className={'Container'}> <div className={'Container'}>
<div className="Notifications" style={{ width: notificationWidth }}> <div className="Notifications">
{this.props.notifications.map((n, i) => ( {this.props.notifications.map((n, i) => (
<React.Fragment key={i}> <React.Fragment key={i}>
{n} {n}
+104 -10
View File
@@ -2,23 +2,40 @@ import React from "react";
import Group from "../../../ui/Group/Group"; import Group from "../../../ui/Group/Group";
import Input from "../../../ui/Input/Input"; import Input from "../../../ui/Input/Input";
import Button from "../../../ui/Button/Button"; import Button from "../../../ui/Button/Button";
import { changeEmail, changePassword } from "../../../api/auth";
import { connect } from "react-redux";
import { addNotification } from "../../../store/ducks/notifications";
import NotificationMessage from "../../../ui/Notifications/NotificationMessage";
import errors from "../../../api/errors";
type Props = {
addNotification: AddNotification
}
type State = { type State = {
oldEmail: string oldEmail: string
email1: string email1: string
email2: string email2: string
error: string emailError: string
oldPassword: string
password1: string
password2: string
passwordError: string
} }
class Security extends React.Component<{}, State> { class Security extends React.Component<Props, State> {
state: State = { state: State = {
oldEmail: '', email1: '', email2: '', oldEmail: '', email1: '', email2: '',
error: '' oldPassword: '', password1: '', password2: '',
emailError: '', passwordError: ''
} }
changeEmail = (e: React.FormEvent) => { changeEmail = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
const { oldEmail, email1, email2 } = this.state; const { oldEmail, email1, email2 } = this.state;
const { addNotification } = this.props;
let error = ''; let error = '';
if(!oldEmail) error += 'Введите старый E-mail\n'; if(!oldEmail) error += 'Введите старый E-mail\n';
if(!email1) error += 'Введите новый E-mail\n'; if(!email1) error += 'Введите новый E-mail\n';
@@ -27,17 +44,64 @@ class Security extends React.Component<{}, State> {
if(oldEmail === email1) error += 'Старый и новый E-mail не должны совпадать'; if(oldEmail === email1) error += 'Старый и новый E-mail не должны совпадать';
if(error) { if(error) {
this.setState({ error }); this.setState({ emailError: error });
return; } else {
changeEmail(oldEmail, email1).then(() => {
addNotification(
<NotificationMessage>
E-mail успешно сменен!
</NotificationMessage>
);
this.setState({
oldEmail: '', email1: '', email2: ''
});
}).catch((err: ApiError) => {
this.setState({ emailError: errors[err.code] })
});
}
}
changePassword = (e: React.FormEvent) => {
e.preventDefault();
const { oldPassword, password1, password2 } = this.state;
const { addNotification } = this.props;
let error = '';
if(!oldPassword) error += 'Введите старый пароль\n';
if(!password1) error += 'Введите новый пароль\n';
if(!password2) error += 'Введите повтор нового пароля\n';
if(password1 !== password2) error += 'Пароли не совпадают\n';
if(oldPassword === password1) error += 'Старый и новый пароль не должны совпадать';
if(error) {
this.setState({ passwordError: error });
} else {
changePassword(oldPassword, password1).then(() => {
addNotification(
<NotificationMessage>
Пароль успешно сменен!
</NotificationMessage>
);
this.setState({
oldPassword: '', password1: '', password2: ''
});
}).catch((err: ApiError) => {
this.setState({ passwordError: errors[err.code] })
});
} }
} }
render() { render() {
const { oldEmail, email1, email2, error } = this.state; const {
oldEmail, email1, email2,
oldPassword, password1, password2,
emailError, passwordError
} = this.state;
return ( return (<>
<Group title={'Смена E-mail'} className={'Settings_Group'}> <Group title={'Смена E-mail'} className={'Settings_Group'}>
{error && <pre className={'Error'}>{error}</pre>} {emailError && <pre className={'Error'}>{emailError}</pre>}
<form onSubmit={this.changeEmail}> <form onSubmit={this.changeEmail}>
<Input <Input
placeholder={'Старый E-mail'} placeholder={'Старый E-mail'}
@@ -60,8 +124,38 @@ class Security extends React.Component<{}, State> {
<Button>Сохранить</Button> <Button>Сохранить</Button>
</form> </form>
</Group> </Group>
);
<Group title={'Смена пароля'} className={'Settings_Group'}>
{passwordError && <pre className={'Error'}>{passwordError}</pre>}
<form onSubmit={this.changePassword}>
<Input
placeholder={'Старый пароль'}
type={'password'}
value={oldPassword}
onChange={e => this.setState({ oldPassword: e.target.value })}
/>
<Input
placeholder={'Новый пароль'}
type={'password'}
value={password1}
onChange={e => this.setState({ password1: e.target.value })}
/>
<Input
placeholder={'Новый пароль ещё раз'}
type={'password'}
value={password2}
onChange={e => this.setState({ password2: e.target.value })}
/>
<Button>Сохранить</Button>
</form>
</Group>
</>);
} }
} }
export default Security; export default connect(
null,
dispatch => ({
addNotification: (notification: React.ReactNode) => dispatch(addNotification(notification))
})
)(Security);
+11 -8
View File
@@ -1,17 +1,20 @@
@import "../../Constants"
.Animate-Notification-exit
opacity: 0
.Notification .Notification
background-color: var(--block-color) background-color: var(--block-color)
border: 1px solid var(--link-color) border: 1px solid var(--link-color)
border-radius: 5px
padding: .5rem padding: .5rem
box-sizing: border-box box-sizing: border-box
max-width: 500px min-width: 288px
width: 500px
cursor: pointer cursor: pointer
margin-top: 1rem margin: 1rem
position: fixed
z-index: 100000
@media (max-width: 500px)
width: 100%
@media (min-width: 501px)
width: 500px
&--Error &--Error
background: var(--error-bg-color) background: var(--error-bg-color)
+42 -11
View File
@@ -5,7 +5,9 @@ import { capitalize } from "../../utils";
type Props = { type Props = {
children: React.ReactNode children: React.ReactNode
level?: 'info' | 'success' | 'error' level?: 'info' | 'success' | 'error'
} hideTime?: number
position?: 'left' | 'bottom' | 'right'
} & React.DetailedHTMLProps<React.AllHTMLAttributes<HTMLDivElement>, HTMLDivElement>
type State = { type State = {
time: number time: number
@@ -16,7 +18,7 @@ class NotificationMessage extends React.Component<Props, State> {
timer: NodeJS.Timeout | undefined; timer: NodeJS.Timeout | undefined;
state = { state = {
time: 5000, paused: false time: this.props.hideTime || 5000, paused: false
}; };
onMouseOver = () => { onMouseOver = () => {
@@ -28,32 +30,60 @@ class NotificationMessage extends React.Component<Props, State> {
}; };
onClick = () => { onClick = () => {
this.timer = setInterval(() => this.interval(80), 1); this.timer = setInterval(() => {
this.interval(80);
}, 1);
this.setState({ paused: false }); this.setState({ paused: false });
}; };
interval = (step = 50) => { interval = (step = 50) => {
const { time, paused } = this.state; const { time, paused } = this.state;
if(time <= 0 && this.timer) clearInterval(this.timer);
if(!paused) this.setState({ time: time - step }); if(time <= 0 && this.timer)
clearInterval(this.timer);
if(!paused)
this.setState({ time: time - step });
}; };
componentDidMount(): void { componentDidMount(): void {
this.timer = setInterval(() => this.interval(10), 1) this.timer = setInterval(() => {
this.interval(10);
}, 10);
}; };
componentWillUnmount(): void { componentWillUnmount(): void {
if(this.timer) clearTimeout(this.timer); if(this.timer)
clearTimeout(this.timer);
} }
render() { render() {
const { time } = this.state; const { time } = this.state;
const { children, level } = this.props; const {
children,
level,
hideTime,
position,
...props
} = this.props;
if(time <= 0) if(time <= 0)
return null; return null;
const styleClass = level ? `Notification--${capitalize(level)}` : ''; const styleClass = level ? `Notification--${capitalize(level)}` : '';
const width = window.screen.width >= 500 ? 500 : window.screen.width;
let style = {};
switch(position) {
default:
case "left":
Object.assign(style, { bottom: 0, left: 0 });
break;
case "bottom":
Object.assign(style, { bottom: 0, left: '50%', transform: 'translateX(-50%)' });
break;
case "right":
Object.assign(style, { bottom: 0, right: 0 });
break;
}
return ( return (
<div <div
@@ -61,10 +91,11 @@ class NotificationMessage extends React.Component<Props, State> {
onMouseOver={this.onMouseOver} onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut} onMouseOut={this.onMouseOut}
onClick={this.onClick} onClick={this.onClick}
style={{ width }} style={style}
{...props}
> >
{children} {children}
<div className="Progress" style={{ width: `${time / 5000 * 100}%` }}/> <div className="Progress" style={{ width: `${time / (5000 || hideTime) * 100}%` }}/>
</div> </div>
); );
} }