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

moved messages to separate reducer

This commit is contained in:
Fedor Katurov 2020-09-08 10:58:55 +07:00
parent bc09810802
commit 737a4396de
14 changed files with 212 additions and 153 deletions

View file

@ -1,21 +1,21 @@
import React, { FC, useState, useCallback, KeyboardEventHandler, useMemo } from 'react';
import React, { FC, KeyboardEventHandler, useCallback, useMemo, useState } from 'react';
import styles from './styles.scss';
import { Textarea } from '~/components/input/Textarea';
import { Filler } from '~/components/containers/Filler';
import { Button } from '~/components/input/Button';
import { Group } from '~/components/containers/Group';
import { selectAuthProfile } from '~/redux/auth/selectors';
import { connect } from 'react-redux';
import { LoaderCircle } from '~/components/input/LoaderCircle';
import * as AUTH_ACTIONS from '~/redux/auth/actions';
import * as MESSAGES_ACTIONS from '~/redux/messages/actions';
import { ERROR_LITERAL } from '~/constants/errors';
import { selectMessages } from '~/redux/messages/selectors';
const mapStateToProps = state => ({
profile: selectAuthProfile(state),
messages: selectMessages(state),
});
const mapDispatchToProps = {
authSendMessage: AUTH_ACTIONS.authSendMessage,
messagesSendMessage: MESSAGES_ACTIONS.messagesSendMessage,
};
type IProps = ReturnType<typeof mapStateToProps> &
@ -26,8 +26,8 @@ type IProps = ReturnType<typeof mapStateToProps> &
};
const MessageFormUnconnected: FC<IProps> = ({
profile: { is_sending_messages, is_loading_messages, messages_error },
authSendMessage,
messages: { is_sending_messages, is_loading_messages, messages_error },
messagesSendMessage,
id = 0,
text: initialText = '',
@ -45,8 +45,8 @@ const MessageFormUnconnected: FC<IProps> = ({
}, [setText, isEditing, onCancel]);
const onSubmit = useCallback(() => {
authSendMessage({ text, id }, onSuccess);
}, [authSendMessage, text, id, onSuccess]);
messagesSendMessage({ text, id }, onSuccess);
}, [messagesSendMessage, text, id, onSuccess]);
const onKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
({ ctrlKey, key }) => {

View file

@ -2,57 +2,62 @@ import React, { FC, useCallback, useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { selectAuthProfile, selectAuthUser } from '~/redux/auth/selectors';
import styles from './styles.scss';
import * as AUTH_ACTIONS from '~/redux/auth/actions';
import * as AUTH_ACTIONS from '~/redux/messages/actions';
import { Message } from '~/components/profile/Message';
import { Group } from '~/components/containers/Group';
import pick from 'ramda/es/pick';
import { NodeNoComments } from '~/components/node/NodeNoComments';
import { selectMessages } from '~/redux/messages/selectors';
const mapStateToProps = state => ({
profile: selectAuthProfile(state),
messages: selectMessages(state),
user: pick(['id'], selectAuthUser(state)),
});
const mapDispatchToProps = {
authGetMessages: AUTH_ACTIONS.authGetMessages,
authDeleteMessage: AUTH_ACTIONS.authDeleteMessage,
messagesGetMessages: AUTH_ACTIONS.messagesGetMessages,
messagesDeleteMessage: AUTH_ACTIONS.messagesDeleteMessage,
};
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const ProfileMessagesUnconnected: FC<IProps> = ({
profile,
messages,
user: { id },
authGetMessages,
authDeleteMessage,
messagesGetMessages,
messagesDeleteMessage,
}) => {
const [editingMessageId, setEditingMessageId] = useState(0);
const onEditMessage = useCallback((id: number) => setEditingMessageId(id), [setEditingMessageId]);
const onCancelEdit = useCallback(() => setEditingMessageId(0), [setEditingMessageId]);
const onDeleteMessage = useCallback((id: number) => authDeleteMessage(id), [authDeleteMessage]);
const onDeleteMessage = useCallback((id: number) => messagesDeleteMessage(id), [
messagesDeleteMessage,
]);
useEffect(() => {
if (profile.is_loading || !profile.user || !profile.user.username) return;
authGetMessages(profile.user.username);
messagesGetMessages(profile.user.username);
}, [profile.user]);
useEffect(() => {
if (profile.is_loading || !profile.user || !profile.user.username || profile.messages_error)
if (profile.is_loading || !profile.user || !profile.user.username || messages.messages_error)
return;
const timer = setTimeout(() => authGetMessages(profile.user.username), 20000);
const timer = setTimeout(() => messagesGetMessages(profile.user.username), 20000);
return () => clearTimeout(timer);
}, [profile.user, profile.messages]);
}, [profile.user, messages.messages]);
if (!profile.messages.length || profile.is_loading)
return <NodeNoComments is_loading={profile.is_loading_messages || profile.is_loading} />;
if (!messages.messages.length || profile.is_loading)
return <NodeNoComments is_loading={messages.is_loading_messages || profile.is_loading} />;
return (
<Group className={styles.messages}>
{profile.messages
{messages.messages
.filter(message => !!message.text)
.map((
message // TODO: show files / memo
@ -68,7 +73,7 @@ const ProfileMessagesUnconnected: FC<IProps> = ({
/>
))}
{!profile.is_loading_messages && profile.messages.length > 0 && (
{!messages.is_loading_messages && messages.messages.length > 0 && (
<div className={styles.placeholder}>Когда-нибудь здесь будут еще сообщения</div>
)}
</Group>

View file

@ -1,6 +1,6 @@
import { AUTH_USER_ACTIONS } from '~/redux/auth/constants';
import { IAuthState, ISocialProvider, IUser } from '~/redux/auth/types';
import { IMessage, IOAuthEvent } from '../types';
import { IOAuthEvent } from '../types';
export const userSendLoginRequest = ({
username,
@ -54,22 +54,6 @@ export const authSetProfile = (profile: Partial<IAuthState['profile']>) => ({
profile,
});
export const authGetMessages = (username: string) => ({
type: AUTH_USER_ACTIONS.GET_MESSAGES,
username,
});
export const authSendMessage = (message: Partial<IMessage>, onSuccess) => ({
type: AUTH_USER_ACTIONS.SEND_MESSAGE,
message,
onSuccess,
});
export const authDeleteMessage = (id: IMessage['id']) => ({
type: AUTH_USER_ACTIONS.DELETE_MESSAGE,
id,
});
export const authSetUpdates = (updates: Partial<IAuthState['updates']>) => ({
type: AUTH_USER_ACTIONS.SET_UPDATES,
updates,

View file

@ -13,10 +13,6 @@ export const AUTH_USER_ACTIONS = {
OPEN_PROFILE: 'OPEN_PROFILE',
LOAD_PROFILE: 'LOAD_PROFILE',
SET_PROFILE: 'SET_PROFILE',
GET_MESSAGES: 'GET_MESSAGES',
SEND_MESSAGE: 'SEND_MESSAGE',
DELETE_MESSAGE: 'DELETE_MESSAGE',
SET_UPDATES: 'SET_UPDATES',
SET_LAST_SEEN_MESSAGES: 'SET_LAST_SEEN_MESSAGES',

View file

@ -26,11 +26,8 @@ const INITIAL_STATE: IAuthState = {
profile: {
tab: 'profile',
is_loading: true,
is_loading_messages: true,
is_sending_messages: false,
user: null,
messages: [],
messages_error: null,
patch_errors: {},
socials: {

View file

@ -3,7 +3,6 @@ import { AUTH_USER_ACTIONS, EMPTY_USER, USER_ERRORS, USER_ROLES } from '~/redux/
import {
authAttachSocial,
authDropSocial,
authGetMessages,
authGotOauthLoginEvent,
authLoadProfile,
authLoggedIn,
@ -12,7 +11,6 @@ import {
authPatchUser,
authRequestRestoreCode,
authRestorePassword,
authSendMessage,
authSendRegisterSocial,
authSetLastSeenMessages,
authSetProfile,
@ -32,9 +30,7 @@ import {
apiAttachSocial,
apiAuthGetUpdates,
apiAuthGetUser,
apiAuthGetUserMessages,
apiAuthGetUserProfile,
apiAuthSendMessage,
apiCheckRestoreCode,
apiDropSocial,
apiGetSocials,
@ -54,13 +50,14 @@ import {
selectAuthUser,
selectToken,
} from './selectors';
import { IMessageNotification, IResultWithStatus, OAUTH_EVENT_TYPES, Unwrap } from '../types';
import { IResultWithStatus, OAUTH_EVENT_TYPES, Unwrap } from '../types';
import { IAuthState, IUser } from './types';
import { REHYDRATE, RehydrateAction } from 'redux-persist';
import { selectModal } from '~/redux/modal/selectors';
import { IModalState } from '~/redux/modal';
import { DIALOGS } from '~/redux/modal/constants';
import { ERRORS } from '~/constants/errors';
import { messagesSetMessages } from '~/redux/messages/actions';
export function* reqWrapper(requestAction, props = {}): ReturnType<typeof requestAction> {
const access = yield select(selectToken);
@ -157,7 +154,8 @@ function* loadProfile({ username }: ReturnType<typeof authLoadProfile>) {
return false;
}
yield put(authSetProfile({ is_loading: false, user, messages: [] }));
yield put(authSetProfile({ is_loading: false, user }));
yield put(messagesSetMessages({ messages: [] }));
return true;
}
@ -172,94 +170,6 @@ function* openProfile({ username, tab = 'profile' }: ReturnType<typeof authOpenP
}
}
function* getMessages({ username }: ReturnType<typeof authGetMessages>) {
// yield put(modalShowDialog(DIALOGS.PROFILE));
const { messages } = yield select(selectAuthProfile);
yield put(
authSetProfile({
is_loading_messages: true,
messages:
messages &&
messages.length > 0 &&
(messages[0].to.username === username || messages[0].from.username === username)
? messages
: [],
})
);
const {
error,
data,
// data: { messages },
} = yield call(reqWrapper, apiAuthGetUserMessages, { username });
if (error || !data.messages) {
return yield put(
authSetProfile({
is_loading_messages: false,
messages_error: ERRORS.EMPTY_RESPONSE,
})
);
}
yield put(authSetProfile({ is_loading_messages: false, messages: data.messages }));
const { notifications } = yield select(selectAuthUpdates);
// clear viewed message from notifcation list
const filtered = notifications.filter(
notification =>
notification.type !== 'message' ||
(notification as IMessageNotification).content.from.username !== username
);
if (filtered.length !== notifications.length) {
yield put(authSetUpdates({ notifications: filtered }));
}
}
function* sendMessage({ message, onSuccess }: ReturnType<typeof authSendMessage>) {
const {
user: { username },
} = yield select(selectAuthProfile);
if (!username) return;
yield put(authSetProfile({ is_sending_messages: true, messages_error: null }));
const { error, data } = yield call(reqWrapper, apiAuthSendMessage, {
username,
message,
});
console.log({ error, data });
if (error || !data.message) {
return yield put(
authSetProfile({
is_sending_messages: false,
messages_error: error || ERRORS.EMPTY_RESPONSE,
})
);
}
const { user, messages } = yield select(selectAuthProfile);
if (user.username !== username) {
return yield put(authSetProfile({ is_sending_messages: false }));
}
yield put(
authSetProfile({
is_sending_messages: false,
messages: [data.message, ...messages],
})
);
onSuccess();
}
function* getUpdates() {
const user: ReturnType<typeof selectAuthUser> = yield select(selectAuthUser);
@ -543,8 +453,6 @@ function* authSaga() {
yield takeLatest(AUTH_USER_ACTIONS.GOT_AUTH_POST_MESSAGE, gotPostMessageSaga);
yield takeLatest(AUTH_USER_ACTIONS.OPEN_PROFILE, openProfile);
yield takeLatest(AUTH_USER_ACTIONS.LOAD_PROFILE, loadProfile);
yield takeLatest(AUTH_USER_ACTIONS.GET_MESSAGES, getMessages);
yield takeLatest(AUTH_USER_ACTIONS.SEND_MESSAGE, sendMessage);
yield takeLatest(AUTH_USER_ACTIONS.SET_LAST_SEEN_MESSAGES, setLastSeenMessages);
yield takeLatest(AUTH_USER_ACTIONS.PATCH_USER, patchUser);
yield takeLatest(AUTH_USER_ACTIONS.REQUEST_RESTORE_CODE, requestRestoreCode);

View file

@ -1,4 +1,4 @@
import { IFile, IMessage, INotification } from '../types';
import { IFile, INotification } from '../types';
export interface IToken {
access: string;
@ -52,12 +52,8 @@ export type IAuthState = Readonly<{
profile: {
tab: 'profile' | 'messages' | 'settings';
is_loading: boolean;
is_loading_messages: boolean;
is_sending_messages: boolean;
user: IUser;
messages: IMessage[];
messages_error: string;
patch_errors: Record<string, string>;
socials: {

View file

@ -0,0 +1,24 @@
import { IMessage } from '~/redux/types';
import { MESSAGES_ACTIONS } from '~/redux/messages/constants';
import { IMessagesState } from '~/redux/messages';
export const messagesGetMessages = (username: string) => ({
type: MESSAGES_ACTIONS.GET_MESSAGES,
username,
});
export const messagesSendMessage = (message: Partial<IMessage>, onSuccess) => ({
type: MESSAGES_ACTIONS.SEND_MESSAGE,
message,
onSuccess,
});
export const messagesDeleteMessage = (id: IMessage['id']) => ({
type: MESSAGES_ACTIONS.DELETE_MESSAGE,
id,
});
export const messagesSetMessages = (messages: Partial<IMessagesState>) => ({
type: MESSAGES_ACTIONS.SET_MESSAGES,
messages,
});

View file

@ -0,0 +1,8 @@
const p = 'MESSAGES.';
export const MESSAGES_ACTIONS = {
SET_MESSAGES: `${p}SET_MESSAGES`,
GET_MESSAGES: `${p}GET_MESSAGES`,
SEND_MESSAGE: `${p}SEND_MESSAGE`,
DELETE_MESSAGE: `${p}DELETE_MESSAGE`,
};

View file

@ -0,0 +1,15 @@
import { MESSAGES_ACTIONS } from '~/redux/messages/constants';
import { IMessagesState } from '~/redux/messages';
import { messagesSetMessages } from '~/redux/messages/actions';
const setMessages = (
state: IMessagesState,
{ messages }: ReturnType<typeof messagesSetMessages>
): IMessagesState => ({
...state,
...messages,
});
export const MESSAGE_HANDLERS = {
[MESSAGES_ACTIONS.SET_MESSAGES]: setMessages,
};

View file

@ -0,0 +1,19 @@
import { createReducer } from '~/utils/reducer';
import { MESSAGE_HANDLERS } from '~/redux/messages/handlers';
import { IMessage } from '~/redux/types';
export interface IMessagesState {
is_loading_messages: boolean;
is_sending_messages: boolean;
messages: IMessage[];
messages_error: string;
}
const INITIAL_STATE: IMessagesState = {
is_loading_messages: true,
is_sending_messages: false,
messages_error: null,
messages: [],
};
export default createReducer(INITIAL_STATE, MESSAGE_HANDLERS);

View file

@ -0,0 +1,98 @@
import { authSetUpdates } from '~/redux/auth/actions';
import { call, put, select, takeLatest } from 'redux-saga/effects';
import { selectAuthProfile, selectAuthUpdates } from '~/redux/auth/selectors';
import { apiAuthGetUserMessages, apiAuthSendMessage } from '~/redux/auth/api';
import { ERRORS } from '~/constants/errors';
import { IMessageNotification } from '~/redux/types';
import { reqWrapper } from '~/redux/auth/sagas';
import { messagesGetMessages, messagesSendMessage, messagesSetMessages, } from '~/redux/messages/actions';
import { MESSAGES_ACTIONS } from '~/redux/messages/constants';
import { selectMessages } from '~/redux/messages/selectors';
function* getMessages({ username }: ReturnType<typeof messagesGetMessages>) {
const { messages }: ReturnType<typeof selectMessages> = yield select(selectMessages);
yield put(
messagesSetMessages({
is_loading_messages: true,
messages:
messages &&
messages.length > 0 &&
(messages[0].to.username === username || messages[0].from.username === username)
? messages
: [],
})
);
const { error, data } = yield call(reqWrapper, apiAuthGetUserMessages, { username });
if (error || !data.messages) {
return yield put(
messagesSetMessages({
is_loading_messages: false,
messages_error: ERRORS.EMPTY_RESPONSE,
})
);
}
yield put(messagesSetMessages({ is_loading_messages: false, messages: data.messages }));
const { notifications } = yield select(selectAuthUpdates);
// clear viewed message from notifcation list
const filtered = notifications.filter(
notification =>
notification.type !== 'message' ||
(notification as IMessageNotification).content.from.username !== username
);
if (filtered.length !== notifications.length) {
yield put(authSetUpdates({ notifications: filtered }));
}
}
function* sendMessage({ message, onSuccess }: ReturnType<typeof messagesSendMessage>) {
const {
user: { username },
}: ReturnType<typeof selectAuthProfile> = yield select(selectAuthProfile);
if (!username) return;
yield put(messagesSetMessages({ is_sending_messages: true, messages_error: null }));
const { error, data } = yield call(reqWrapper, apiAuthSendMessage, {
username,
message,
});
if (error || !data.message) {
return yield put(
messagesSetMessages({
is_sending_messages: false,
messages_error: error || ERRORS.EMPTY_RESPONSE,
})
);
}
const { user }: ReturnType<typeof selectAuthProfile> = yield select(selectAuthProfile);
if (user.username !== username) {
return yield put(messagesSetMessages({ is_sending_messages: false }));
}
const { messages }: ReturnType<typeof selectMessages> = yield select(selectMessages);
yield put(
messagesSetMessages({
is_sending_messages: false,
messages: [data.message, ...messages],
})
);
onSuccess();
}
export default function*() {
yield takeLatest(MESSAGES_ACTIONS.GET_MESSAGES, getMessages);
yield takeLatest(MESSAGES_ACTIONS.SEND_MESSAGE, sendMessage);
}

View file

@ -0,0 +1,3 @@
import { IState } from '~/redux/store';
export const selectMessages = (state: IState) => state.messages;

View file

@ -1,9 +1,9 @@
import { createStore, applyMiddleware, combineReducers, compose, Store } from 'redux';
import { applyMiddleware, combineReducers, compose, createStore, Store } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import { persistReducer, persistStore } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import createSagaMiddleware from 'redux-saga';
import { connectRouter, RouterState, routerMiddleware } from 'connected-react-router';
import { connectRouter, routerMiddleware, RouterState } from 'connected-react-router';
import { createBrowserHistory } from 'history';
import { PersistConfig, Persistor } from 'redux-persist/es/types';
@ -26,11 +26,14 @@ import playerSaga from '~/redux/player/sagas';
import modal, { IModalState } from '~/redux/modal';
import { modalSaga } from './modal/sagas';
import { gotAuthPostMessage, authOpenProfile } from './auth/actions';
import { authOpenProfile, gotAuthPostMessage } from './auth/actions';
import boris, { IBorisState } from './boris/reducer';
import borisSaga from './boris/sagas';
import messages, { IMessagesState } from './messages';
import messagesSaga from './messages/sagas';
const authPersistConfig: PersistConfig = {
key: 'auth',
whitelist: ['token', 'user', 'updates'],
@ -58,6 +61,7 @@ export interface IState {
flow: IFlowState;
player: IPlayerState;
boris: IBorisState;
messages: IMessagesState;
}
export const sagaMiddleware = createSagaMiddleware();
@ -78,6 +82,7 @@ export const store = createStore(
uploads,
flow: persistReducer(flowPersistConfig, flow),
player: persistReducer(playerPersistConfig, player),
messages,
}),
composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware))
);
@ -93,6 +98,7 @@ export function configureStore(): {
sagaMiddleware.run(playerSaga);
sagaMiddleware.run(modalSaga);
sagaMiddleware.run(borisSaga);
sagaMiddleware.run(messagesSaga);
window.addEventListener('message', message => {
if (message && message.data && message.data.type === 'oauth_login' && message.data.token)