From 60dffc235343651842f18ae50b4c28e84698595e Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Tue, 19 Nov 2019 15:56:45 +0700 Subject: [PATCH] fixed profile patching --- .../containers/CoverBackdrop/index.tsx | 22 ++-- .../profile/ProfileAvatar/index.tsx | 106 ++++++++++++++++++ .../profile/ProfileAvatar/styles.scss | 11 ++ src/containers/profile/ProfileInfo/index.tsx | 36 +++--- src/redux/auth/api.ts | 39 ++++--- src/redux/auth/sagas.ts | 55 +++++---- 6 files changed, 209 insertions(+), 60 deletions(-) create mode 100644 src/containers/profile/ProfileAvatar/index.tsx create mode 100644 src/containers/profile/ProfileAvatar/styles.scss diff --git a/src/components/containers/CoverBackdrop/index.tsx b/src/components/containers/CoverBackdrop/index.tsx index f0cfa340..3d8e12b0 100644 --- a/src/components/containers/CoverBackdrop/index.tsx +++ b/src/components/containers/CoverBackdrop/index.tsx @@ -1,15 +1,17 @@ -import React, { FC, useState, useCallback, useEffect } from 'react'; -import { IUser } from '~/redux/auth/types'; -import styles from './styles.scss'; -import { getURL } from '~/utils/dom'; -import { PRESETS } from '~/constants/urls'; -import classNames from 'classnames'; +import React, { FC, useState, useCallback, useEffect, useRef } from "react"; +import { IUser } from "~/redux/auth/types"; +import styles from "./styles.scss"; +import { getURL } from "~/utils/dom"; +import { PRESETS } from "~/constants/urls"; +import classNames from "classnames"; interface IProps { - cover: IUser['cover']; + cover: IUser["cover"]; } const CoverBackdrop: FC = ({ cover }) => { + const ref = useRef(); + const [is_loaded, setIsLoaded] = useState(false); const onLoad = useCallback(() => setIsLoaded(true), [setIsLoaded]); @@ -17,7 +19,11 @@ const CoverBackdrop: FC = ({ cover }) => { const image = getURL(cover, PRESETS.cover); useEffect(() => { + if (!cover || !cover.url || !ref || !ref.current) return; + + ref.current.src = ""; setIsLoaded(false); + ref.current.src = getURL(cover, PRESETS.cover); }, [cover]); if (!cover) return null; @@ -27,7 +33,7 @@ const CoverBackdrop: FC = ({ cover }) => { className={classNames(styles.cover, { [styles.active]: is_loaded })} style={{ backgroundImage: `url("${image}")` }} > - + ); }; diff --git a/src/containers/profile/ProfileAvatar/index.tsx b/src/containers/profile/ProfileAvatar/index.tsx new file mode 100644 index 00000000..0e549a40 --- /dev/null +++ b/src/containers/profile/ProfileAvatar/index.tsx @@ -0,0 +1,106 @@ +import React, { FC, useCallback, useEffect, useState } from "react"; +import styles from "./styles.scss"; +import { connect } from "react-redux"; +import { getURL } from "~/utils/dom"; +import pick from "ramda/es/pick"; +import { selectAuthUser, selectAuthProfile } from "~/redux/auth/selectors"; +import { PRESETS } from "~/constants/urls"; +import { selectUploads } from "~/redux/uploads/selectors"; +import { IFileWithUUID } from "~/redux/types"; +import uuid from "uuid4"; +import { + UPLOAD_SUBJECTS, + UPLOAD_TARGETS, + UPLOAD_TYPES +} from "~/redux/uploads/constants"; +import path from "ramda/es/path"; +import * as UPLOAD_ACTIONS from "~/redux/uploads/actions"; +import * as AUTH_ACTIONS from "~/redux/auth/actions"; + +const mapStateToProps = state => ({ + user: pick(["id"], selectAuthUser(state)), + profile: pick(["is_loading", "user"], selectAuthProfile(state)), + uploads: pick(["statuses", "files"], selectUploads(state)) +}); + +const mapDispatchToProps = { + uploadUploadFiles: UPLOAD_ACTIONS.uploadUploadFiles, + authPatchUser: AUTH_ACTIONS.authPatchUser +}; + +type IProps = ReturnType & + typeof mapDispatchToProps & {}; + +const ProfileAvatarUnconnected: FC = ({ + user: { id }, + profile: { is_loading, user }, + uploads: { statuses, files }, + uploadUploadFiles, + authPatchUser +}) => { + const can_edit = !is_loading && id && id === user.id; + + const [temp, setTemp] = useState(null); + + useEffect(() => { + if (!can_edit) return; + + Object.entries(statuses).forEach(([id, status]) => { + if (temp === id && !!status.uuid && files[status.uuid]) { + authPatchUser({ photo: files[status.uuid] }); + setTemp(null); + } + }); + }, [statuses, files, temp, can_edit, authPatchUser]); + + const onUpload = useCallback( + (uploads: File[]) => { + const items: IFileWithUUID[] = Array.from(uploads).map( + (file: File): IFileWithUUID => ({ + file, + temp_id: uuid(), + subject: UPLOAD_SUBJECTS.AVATAR, + target: UPLOAD_TARGETS.PROFILES, + type: UPLOAD_TYPES.IMAGE + }) + ); + + setTemp(path([0, "temp_id"], items)); + uploadUploadFiles(items.slice(0, 1)); + }, + [uploadUploadFiles, setTemp] + ); + + const onInputChange = useCallback( + event => { + if (!can_edit) return; + + event.preventDefault(); + + if (!event.target.files || !event.target.files.length) return; + + onUpload(Array.from(event.target.files)); + }, + [onUpload, can_edit] + ); + + return ( +
+ {can_edit && } +
+ ); +}; + +const ProfileAvatar = connect( + mapStateToProps, + mapDispatchToProps +)(ProfileAvatarUnconnected); + +export { ProfileAvatar }; diff --git a/src/containers/profile/ProfileAvatar/styles.scss b/src/containers/profile/ProfileAvatar/styles.scss new file mode 100644 index 00000000..c6fbb6dc --- /dev/null +++ b/src/containers/profile/ProfileAvatar/styles.scss @@ -0,0 +1,11 @@ +.avatar { + @include outer_shadow(); + + border-radius: $radius; + width: 100px; + height: 100px; + background: $content_bg 50% 50% no-repeat/cover; + position: absolute; + bottom: 0; + left: $gap; +} diff --git a/src/containers/profile/ProfileInfo/index.tsx b/src/containers/profile/ProfileInfo/index.tsx index 3e1c9db4..08eeb626 100644 --- a/src/containers/profile/ProfileInfo/index.tsx +++ b/src/containers/profile/ProfileInfo/index.tsx @@ -1,12 +1,13 @@ -import React, { FC } from 'react'; -import { IUser } from '~/redux/auth/types'; -import styles from './styles.scss'; -import { Group } from '~/components/containers/Group'; -import { Placeholder } from '~/components/placeholders/Placeholder'; -import { getURL, getPrettyDate } from '~/utils/dom'; -import { PRESETS } from '~/constants/urls'; -import { ProfileTabs } from '../ProfileTabs'; -import { MessageForm } from '~/components/profile/MessageForm'; +import React, { FC } from "react"; +import { IUser } from "~/redux/auth/types"; +import styles from "./styles.scss"; +import { Group } from "~/components/containers/Group"; +import { Placeholder } from "~/components/placeholders/Placeholder"; +import { getURL, getPrettyDate } from "~/utils/dom"; +import { PRESETS } from "~/constants/urls"; +import { ProfileTabs } from "../ProfileTabs"; +import { MessageForm } from "~/components/profile/MessageForm"; +import { ProfileAvatar } from "../ProfileAvatar"; interface IProps { user?: IUser; @@ -19,24 +20,21 @@ interface IProps { } const TAB_HEADERS = { - messages: , + messages: }; const ProfileInfo: FC = ({ user, tab, is_loading, is_own, setTab }) => (
-
+
- {is_loading ? : user.fullname || user.username} + {is_loading ? ( + + ) : ( + user.fullname || user.username + )}
diff --git a/src/redux/auth/api.ts b/src/redux/auth/api.ts index 5dfa2f1b..2c4cc485 100644 --- a/src/redux/auth/api.ts +++ b/src/redux/auth/api.ts @@ -1,12 +1,17 @@ -import { api, errorMiddleware, resultMiddleware, configWithToken } from '~/utils/api'; -import { API } from '~/constants/api'; -import { IResultWithStatus, IMessage } from '~/redux/types'; -import { userLoginTransform } from '~/redux/auth/transforms'; -import { IUser } from './types'; +import { + api, + errorMiddleware, + resultMiddleware, + configWithToken +} from "~/utils/api"; +import { API } from "~/constants/api"; +import { IResultWithStatus, IMessage } from "~/redux/types"; +import { userLoginTransform } from "~/redux/auth/transforms"; +import { IUser } from "./types"; export const apiUserLogin = ({ username, - password, + password }: { username: string; password: string; @@ -17,7 +22,9 @@ export const apiUserLogin = ({ .catch(errorMiddleware) .then(userLoginTransform); -export const apiAuthGetUser = ({ access }): Promise> => +export const apiAuthGetUser = ({ + access +}): Promise> => api .get(API.USER.ME, configWithToken(access)) .then(resultMiddleware) @@ -25,7 +32,7 @@ export const apiAuthGetUser = ({ access }): Promise> => api .get(API.USER.PROFILE(username), configWithToken(access)) @@ -34,7 +41,7 @@ export const apiAuthGetUserProfile = ({ export const apiAuthGetUserMessages = ({ access, - username, + username }): Promise> => api .get(API.USER.MESSAGES(username), configWithToken(access)) @@ -44,7 +51,7 @@ export const apiAuthGetUserMessages = ({ export const apiAuthSendMessage = ({ access, username, - message, + message }): Promise> => api .post(API.USER.MESSAGE_SEND(username), { message }, configWithToken(access)) @@ -54,14 +61,20 @@ export const apiAuthSendMessage = ({ export const apiAuthGetUpdates = ({ access, exclude_dialogs, - last, + last }): Promise> => api - .get(API.USER.GET_UPDATES, configWithToken(access, { params: { exclude_dialogs, last } })) + .get( + API.USER.GET_UPDATES, + configWithToken(access, { params: { exclude_dialogs, last } }) + ) .then(resultMiddleware) .catch(errorMiddleware); -export const apiUpdateUser = ({ access, user }): Promise> => +export const apiUpdateUser = ({ + access, + user +}): Promise> => api .patch(API.USER.ME, { user }, configWithToken(access)) .then(resultMiddleware) diff --git a/src/redux/auth/sagas.ts b/src/redux/auth/sagas.ts index 7a768283..8db331ea 100644 --- a/src/redux/auth/sagas.ts +++ b/src/redux/auth/sagas.ts @@ -1,4 +1,11 @@ -import { call, put, takeLatest, select, delay } from "redux-saga/effects"; +import { + call, + put, + takeEvery, + takeLatest, + select, + delay +} from "redux-saga/effects"; import { AUTH_USER_ACTIONS, EMPTY_USER, @@ -92,26 +99,32 @@ function* sendLoginRequestSaga({ } function* refreshUser() { - const { - error, - data: { user } - }: IResultWithStatus<{ user: IUser }> = yield call( - reqWrapper, - apiAuthGetUser - ); - - if (error) { - yield put( - authSetUser({ - ...EMPTY_USER, - is_user: false - }) + try { + const { + error, + data: { user } + }: IResultWithStatus<{ user: IUser }> = yield call( + reqWrapper, + apiAuthGetUser ); - return; - } + if (error) { + yield put( + authSetUser({ + ...EMPTY_USER, + is_user: false + }) + ); - yield put(authSetUser({ ...user, is_user: true })); + return; + } + + yield put(authSetUser({ ...user, is_user: true })); + } catch (e) { + console.log("erro", e); + } finally { + console.log("ended"); + } } function* checkUserSaga({ key }: RehydrateAction) { @@ -315,12 +328,14 @@ function* patchUser({ user }: ReturnType) { return yield put(authSetProfile({ patch_errors: data.errors })); } - yield put(authSetUser(data.user)); + console.log({ me, data }); + + yield put(authSetUser({ ...me, ...data.user })); yield put(authSetProfile({ user: { ...me, ...data.user }, tab: "profile" })); } function* authSaga() { - yield takeLatest(REHYDRATE, checkUserSaga); + yield takeEvery(REHYDRATE, checkUserSaga); yield takeLatest([REHYDRATE, AUTH_USER_ACTIONS.LOGGED_IN], startPollingSaga); yield takeLatest(AUTH_USER_ACTIONS.LOGOUT, logoutSaga);