1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 04:46:40 +07:00

user settings mechanism

This commit is contained in:
Fedor Katurov 2019-11-13 18:02:09 +07:00
parent 5fe0deca17
commit a90285a4ac
10 changed files with 162 additions and 4 deletions

View file

@ -30,7 +30,6 @@ type IProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
const Textarea = memo<IProps>( const Textarea = memo<IProps>(
({ ({
value,
placeholder, placeholder,
className, className,
minRows = 3, minRows = 3,
@ -40,6 +39,7 @@ const Textarea = memo<IProps>(
title = '', title = '',
status = '', status = '',
seamless, seamless,
value,
...props ...props
}) => { }) => {
const [rows, setRows] = useState(minRows || 1); const [rows, setRows] = useState(minRows || 1);
@ -97,7 +97,7 @@ const Textarea = memo<IProps>(
<div className={styles.input}> <div className={styles.input}>
<textarea <textarea
rows={rows} rows={rows}
value={value} value={value || ''}
placeholder={placeholder} placeholder={placeholder}
className={classNames(styles.textarea, className)} className={classNames(styles.textarea, className)}
onChange={onInput} onChange={onInput}

View file

@ -1,5 +1,4 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { IUser } from '~/redux/auth/types';
import { formatText } from '~/utils/dom'; import { formatText } from '~/utils/dom';
import styles from './styles.scss'; import styles from './styles.scss';
import { connect } from 'react-redux'; import { connect } from 'react-redux';

View file

@ -0,0 +1,121 @@
import React, { FC, useState, useEffect, useCallback } from 'react';
import styles from './styles.scss';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { selectAuthUser, selectAuthProfile } from '~/redux/auth/selectors';
import { Textarea } from '~/components/input/Textarea';
import { Button } from '~/components/input/Button';
import { Group } from '~/components/containers/Group';
import { Filler } from '~/components/containers/Filler';
import { TextInput } from '~/components/input/TextInput';
import { InputText } from '~/components/input/InputText';
import reject from 'ramda/es/reject';
import * as AUTH_ACTIONS from '~/redux/auth/actions';
import { ERROR_LITERAL } from '~/constants/errors';
const mapStateToProps = state => ({
user: selectAuthUser(state),
profile: selectAuthProfile(state),
});
const mapDispatchToProps = {
authPatchUser: AUTH_ACTIONS.authPatchUser,
};
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const ProfileSettingsUnconnected: FC<IProps> = ({
user,
authPatchUser,
profile: { patch_errors },
}) => {
const [password, setPassword] = useState('');
const [new_password, setNewPassword] = useState('');
const [data, setData] = useState(user);
const setDescription = useCallback(description => setData({ ...data, description }), [
data,
setData,
]);
const setEmail = useCallback(email => setData({ ...data, email }), [data, setData]);
const setUsername = useCallback(username => setData({ ...data, username }), [data, setData]);
const onSubmit = useCallback(
event => {
event.preventDefault();
const fields = reject(el => !el)({
email: data.email !== user.email && data.email,
username: data.username !== user.username && data.username,
password: password.length > 0 && password,
new_password: new_password.length > 0 && new_password,
description: data.description !== user.description && data.description,
});
if (Object.values(fields).length === 0) return;
authPatchUser(fields);
},
[data, password, new_password, authPatchUser]
);
return (
<form className={styles.wrap} onSubmit={onSubmit}>
<Group>
<Textarea value={data.description} handler={setDescription} title="Описание" />
<div className={styles.small}>
Описание будет видно на странице профиля. Здесь работают те же правила оформления, что и в
комментариях.
</div>
<Group className={styles.pad}>
<InputText
value={data.username}
handler={setUsername}
title="Логин"
error={patch_errors.username && ERROR_LITERAL[patch_errors.username]}
/>
<InputText value={data.email} handler={setEmail} title="E-mail" />
<InputText
value={new_password}
handler={setNewPassword}
title="Новый пароль"
type="password"
error={patch_errors.new_password && ERROR_LITERAL[patch_errors.new_password]}
/>
<div />
<InputText
value={password}
handler={setPassword}
title="Старый пароль"
type="password"
error={patch_errors.password && ERROR_LITERAL[patch_errors.password]}
/>
<div className={styles.small}>
Чтобы изменить любое из этих полей, нужно ввести старый пароль.
</div>
</Group>
<Group horizontal>
<Filler />
<Button title="Сохранить" type="submit" />
</Group>
</Group>
</form>
);
};
const ProfileSettings = connect(
mapStateToProps,
mapDispatchToProps
)(ProfileSettingsUnconnected);
export { ProfileSettings };

View file

@ -0,0 +1,14 @@
.wrap {
padding: $gap;
}
.pad {
padding: $gap;
box-shadow: transparentize($color: $red, $amount: 0.5) 0 0 0 2px;
border-radius: $radius;
}
.small {
font: $font_12_regular;
padding: 0 $gap $gap;
}

View file

@ -10,10 +10,12 @@ import * as AUTH_ACTIONS from '~/redux/auth/actions';
import { IAuthState } from '~/redux/auth/types'; import { IAuthState } from '~/redux/auth/types';
import pick from 'ramda/es/pick'; import pick from 'ramda/es/pick';
import { CoverBackdrop } from '~/components/containers/CoverBackdrop'; import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
import { ProfileSettings } from '~/components/profile/ProfileSettings';
const TAB_CONTENT = { const TAB_CONTENT = {
profile: <ProfileDescription />, profile: <ProfileDescription />,
messages: <ProfileMessages />, messages: <ProfileMessages />,
settings: <ProfileSettings />,
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({

View file

@ -71,3 +71,8 @@ export const authSetLastSeenMessages = (
type: AUTH_USER_ACTIONS.SET_LAST_SEEN_MESSAGES, type: AUTH_USER_ACTIONS.SET_LAST_SEEN_MESSAGES,
last_seen_messages, last_seen_messages,
}); });
export const authPatchUser = (user: Partial<IUser>) => ({
type: AUTH_USER_ACTIONS.PATCH_USER,
user,
});

View file

@ -17,6 +17,7 @@ export const AUTH_USER_ACTIONS = {
SET_UPDATES: 'SET_UPDATES', SET_UPDATES: 'SET_UPDATES',
SET_LAST_SEEN_MESSAGES: 'SET_LAST_SEEN_MESSAGES', SET_LAST_SEEN_MESSAGES: 'SET_LAST_SEEN_MESSAGES',
PATCH_USER: 'PATCH_USER',
}; };
export const USER_ERRORS = { export const USER_ERRORS = {

View file

@ -29,6 +29,7 @@ const INITIAL_STATE: IAuthState = {
user: null, user: null,
messages: [], messages: [],
messages_error: null, messages_error: null,
patch_errors: {},
}, },
}; };

View file

@ -13,6 +13,7 @@ import {
authSetUpdates, authSetUpdates,
authLoggedIn, authLoggedIn,
authSetLastSeenMessages, authSetLastSeenMessages,
authPatchUser,
} from '~/redux/auth/actions'; } from '~/redux/auth/actions';
import { import {
apiUserLogin, apiUserLogin,
@ -90,7 +91,7 @@ function* refreshUser() {
function* checkUserSaga({ key }: RehydrateAction) { function* checkUserSaga({ key }: RehydrateAction) {
if (key !== 'auth') return; if (key !== 'auth') return;
yield call(refreshUser); yield call(refreshUser);
// yield put(authOpenProfile('gvorcek')); yield put(authOpenProfile('gvorcek', 'settings'));
} }
function* gotPostMessageSaga({ token }: ReturnType<typeof gotAuthPostMessage>) { function* gotPostMessageSaga({ token }: ReturnType<typeof gotAuthPostMessage>) {
@ -256,6 +257,17 @@ function* setLastSeenMessages({ last_seen_messages }: ReturnType<typeof authSetL
yield call(reqWrapper, apiUpdateUser, { user: { last_seen_messages } }); yield call(reqWrapper, apiUpdateUser, { user: { last_seen_messages } });
} }
function* patchUser({ user }: ReturnType<typeof authPatchUser>) {
const { error, data } = yield call(reqWrapper, apiUpdateUser, { user });
if (error || !data.user || data.errors) {
return yield put(authSetProfile({ patch_errors: data.errors }));
}
yield put(authSetUser(data.user));
yield put(authSetProfile({ user: { ...data.user }, tab: 'profile' }));
}
function* authSaga() { function* authSaga() {
yield takeLatest(REHYDRATE, checkUserSaga); yield takeLatest(REHYDRATE, checkUserSaga);
yield takeLatest([REHYDRATE, AUTH_USER_ACTIONS.LOGGED_IN], startPollingSaga); yield takeLatest([REHYDRATE, AUTH_USER_ACTIONS.LOGGED_IN], startPollingSaga);
@ -267,6 +279,7 @@ function* authSaga() {
yield takeLatest(AUTH_USER_ACTIONS.GET_MESSAGES, getMessages); yield takeLatest(AUTH_USER_ACTIONS.GET_MESSAGES, getMessages);
yield takeLatest(AUTH_USER_ACTIONS.SEND_MESSAGE, sendMessage); yield takeLatest(AUTH_USER_ACTIONS.SEND_MESSAGE, sendMessage);
yield takeLatest(AUTH_USER_ACTIONS.SET_LAST_SEEN_MESSAGES, setLastSeenMessages); yield takeLatest(AUTH_USER_ACTIONS.SET_LAST_SEEN_MESSAGES, setLastSeenMessages);
yield takeLatest(AUTH_USER_ACTIONS.PATCH_USER, patchUser);
} }
export default authSaga; export default authSaga;

View file

@ -46,5 +46,7 @@ export type IAuthState = Readonly<{
user: IUser; user: IUser;
messages: IMessage[]; messages: IMessage[];
messages_error: string; messages_error: string;
patch_errors: Record<string, string>;
}; };
}>; }>;