diff --git a/src/api/auth.ts b/src/api/auth.ts index 829a4a0..17a081b 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -13,3 +13,11 @@ export const login = (login: string, password: string, recaptcha: string) => pos recaptcha }); export const userLogout = () => post('auth/logout'); +export const changeEmail = (old_email: string, new_email: string) => post('auth/changeEmail', { + old_email, + new_email +}); +export const changePassword = (old_password: string, new_password: string) => post('auth/changePassword', { + old_password, + new_password +}); diff --git a/src/api/errors.ts b/src/api/errors.ts index 6b65fcc..c1ff2aa 100644 --- a/src/api/errors.ts +++ b/src/api/errors.ts @@ -6,7 +6,9 @@ const errors: { [key: number]: string } = { 6: 'Токен истек!', 8: 'Новый и старый пароли совпадают!', 9: 'Необходима проверка ReCaptcha', + 10: 'Неверно указан старый E-Mail', + 11: 'Неверно указан старый пароль', 100: 'Не найдено!' }; -export default errors; \ No newline at end of file +export default errors; diff --git a/src/pages/App/App.sass b/src/pages/App/App.sass index aae8d2e..b5145ca 100644 --- a/src/pages/App/App.sass +++ b/src/pages/App/App.sass @@ -85,11 +85,6 @@ a margin: .5rem auto .Notifications - position: fixed - bottom: 0 - left: 0 - z-index: 100000 - @media (min-width: 500px) padding: 1rem box-sizing: border-box diff --git a/src/pages/App/App.tsx b/src/pages/App/App.tsx index b05538f..5a61abc 100644 --- a/src/pages/App/App.tsx +++ b/src/pages/App/App.tsx @@ -43,11 +43,17 @@ class App extends React.Component { constructor(props: Props) { super(props); + parser.registerTag('spoiler', SpoilerTag); + parser.registerTag('color', ColorTag); + } + + componentDidMount() { + const { setUser, setSettings } = this.props; + const cachedTheme = localStorage.getItem('theme'); if(cachedTheme) document.body.setAttribute('theme', cachedTheme); - const { setUser, setSettings } = props; fetchCurrentUser().then(user => { setUser(user); fetchUserSettings().then(settings => { @@ -62,9 +68,6 @@ class App extends React.Component { console.error(err.text); }); - parser.registerTag('spoiler', SpoilerTag); - parser.registerTag('color', ColorTag); - getVersion().then(res => { if(version !== res.version) { console.debug(`Current back-end version: ${res.version}. Clearing cache`); @@ -80,13 +83,12 @@ class App extends React.Component { render() { const { modal } = this.props; - const notificationWidth = window.screen.width >= 500 ? 500 : window.screen.width; return ( <>
-
+
{this.props.notifications.map((n, i) => ( {n} diff --git a/src/pages/User/Settings/Security.tsx b/src/pages/User/Settings/Security.tsx index a28367d..4b3e40b 100644 --- a/src/pages/User/Settings/Security.tsx +++ b/src/pages/User/Settings/Security.tsx @@ -2,23 +2,40 @@ import React from "react"; import Group from "../../../ui/Group/Group"; import Input from "../../../ui/Input/Input"; 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 = { oldEmail: string email1: 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 { state: State = { oldEmail: '', email1: '', email2: '', - error: '' + oldPassword: '', password1: '', password2: '', + emailError: '', passwordError: '' } changeEmail = (e: React.FormEvent) => { e.preventDefault(); const { oldEmail, email1, email2 } = this.state; + const { addNotification } = this.props; + let error = ''; if(!oldEmail) 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(error) { - this.setState({ error }); - return; + this.setState({ emailError: error }); + } else { + changeEmail(oldEmail, email1).then(() => { + addNotification( + + E-mail успешно сменен! + + ); + 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( + + Пароль успешно сменен! + + ); + this.setState({ + oldPassword: '', password1: '', password2: '' + }); + }).catch((err: ApiError) => { + this.setState({ passwordError: errors[err.code] }) + }); } } render() { - const { oldEmail, email1, email2, error } = this.state; + const { + oldEmail, email1, email2, + oldPassword, password1, password2, + emailError, passwordError + } = this.state; - return ( + return (<> - {error &&
{error}
} + {emailError &&
{emailError}
}
{
- ); + + + {passwordError &&
{passwordError}
} +
+ this.setState({ oldPassword: e.target.value })} + /> + this.setState({ password1: e.target.value })} + /> + this.setState({ password2: e.target.value })} + /> + +
+
+ ); } } -export default Security; +export default connect( + null, + dispatch => ({ + addNotification: (notification: React.ReactNode) => dispatch(addNotification(notification)) + }) +)(Security); diff --git a/src/ui/Notifications/NotificationMessage.sass b/src/ui/Notifications/NotificationMessage.sass index 24444c6..6e06ab6 100644 --- a/src/ui/Notifications/NotificationMessage.sass +++ b/src/ui/Notifications/NotificationMessage.sass @@ -1,17 +1,20 @@ -@import "../../Constants" - -.Animate-Notification-exit - opacity: 0 - .Notification background-color: var(--block-color) border: 1px solid var(--link-color) + border-radius: 5px padding: .5rem box-sizing: border-box - max-width: 500px - width: 500px + min-width: 288px 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 background: var(--error-bg-color) @@ -24,4 +27,4 @@ .Progress height: 5px - background: white \ No newline at end of file + background: white diff --git a/src/ui/Notifications/NotificationMessage.tsx b/src/ui/Notifications/NotificationMessage.tsx index d0053e1..7b40311 100644 --- a/src/ui/Notifications/NotificationMessage.tsx +++ b/src/ui/Notifications/NotificationMessage.tsx @@ -5,7 +5,9 @@ import { capitalize } from "../../utils"; type Props = { children: React.ReactNode level?: 'info' | 'success' | 'error' -} + hideTime?: number + position?: 'left' | 'bottom' | 'right' +} & React.DetailedHTMLProps, HTMLDivElement> type State = { time: number @@ -16,7 +18,7 @@ class NotificationMessage extends React.Component { timer: NodeJS.Timeout | undefined; state = { - time: 5000, paused: false + time: this.props.hideTime || 5000, paused: false }; onMouseOver = () => { @@ -28,32 +30,60 @@ class NotificationMessage extends React.Component { }; onClick = () => { - this.timer = setInterval(() => this.interval(80), 1); + this.timer = setInterval(() => { + this.interval(80); + }, 1); this.setState({ paused: false }); }; interval = (step = 50) => { 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 { - this.timer = setInterval(() => this.interval(10), 1) + this.timer = setInterval(() => { + this.interval(10); + }, 10); }; componentWillUnmount(): void { - if(this.timer) clearTimeout(this.timer); + if(this.timer) + clearTimeout(this.timer); } render() { const { time } = this.state; - const { children, level } = this.props; + const { + children, + level, + hideTime, + position, + ...props + } = this.props; + if(time <= 0) return null; 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 (
{ onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} onClick={this.onClick} - style={{ width }} + style={style} + {...props} > {children} -
+
); }