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

fixed profile patching

This commit is contained in:
Fedor Katurov 2019-11-19 15:56:45 +07:00
parent f25d627b4e
commit 60dffc2353
6 changed files with 209 additions and 60 deletions

View file

@ -1,15 +1,17 @@
import React, { FC, useState, useCallback, useEffect } from 'react'; import React, { FC, useState, useCallback, useEffect, useRef } from "react";
import { IUser } from '~/redux/auth/types'; import { IUser } from "~/redux/auth/types";
import styles from './styles.scss'; import styles from "./styles.scss";
import { getURL } from '~/utils/dom'; import { getURL } from "~/utils/dom";
import { PRESETS } from '~/constants/urls'; import { PRESETS } from "~/constants/urls";
import classNames from 'classnames'; import classNames from "classnames";
interface IProps { interface IProps {
cover: IUser['cover']; cover: IUser["cover"];
} }
const CoverBackdrop: FC<IProps> = ({ cover }) => { const CoverBackdrop: FC<IProps> = ({ cover }) => {
const ref = useRef<HTMLImageElement>();
const [is_loaded, setIsLoaded] = useState(false); const [is_loaded, setIsLoaded] = useState(false);
const onLoad = useCallback(() => setIsLoaded(true), [setIsLoaded]); const onLoad = useCallback(() => setIsLoaded(true), [setIsLoaded]);
@ -17,7 +19,11 @@ const CoverBackdrop: FC<IProps> = ({ cover }) => {
const image = getURL(cover, PRESETS.cover); const image = getURL(cover, PRESETS.cover);
useEffect(() => { useEffect(() => {
if (!cover || !cover.url || !ref || !ref.current) return;
ref.current.src = "";
setIsLoaded(false); setIsLoaded(false);
ref.current.src = getURL(cover, PRESETS.cover);
}, [cover]); }, [cover]);
if (!cover) return null; if (!cover) return null;
@ -27,7 +33,7 @@ const CoverBackdrop: FC<IProps> = ({ cover }) => {
className={classNames(styles.cover, { [styles.active]: is_loaded })} className={classNames(styles.cover, { [styles.active]: is_loaded })}
style={{ backgroundImage: `url("${image}")` }} style={{ backgroundImage: `url("${image}")` }}
> >
<img src={image} onLoad={onLoad} /> <img onLoad={onLoad} ref={ref} />
</div> </div>
); );
}; };

View file

@ -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 mapStateToProps> &
typeof mapDispatchToProps & {};
const ProfileAvatarUnconnected: FC<IProps> = ({
user: { id },
profile: { is_loading, user },
uploads: { statuses, files },
uploadUploadFiles,
authPatchUser
}) => {
const can_edit = !is_loading && id && id === user.id;
const [temp, setTemp] = useState<string>(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 (
<div
className={styles.avatar}
style={{
backgroundImage: is_loading
? null
: `url("${user && getURL(user.photo, PRESETS.avatar)}")`
}}
>
{can_edit && <input type="file" onInput={onInputChange} />}
</div>
);
};
const ProfileAvatar = connect(
mapStateToProps,
mapDispatchToProps
)(ProfileAvatarUnconnected);
export { ProfileAvatar };

View file

@ -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;
}

View file

@ -1,12 +1,13 @@
import React, { FC } from 'react'; import React, { FC } from "react";
import { IUser } from '~/redux/auth/types'; import { IUser } from "~/redux/auth/types";
import styles from './styles.scss'; import styles from "./styles.scss";
import { Group } from '~/components/containers/Group'; import { Group } from "~/components/containers/Group";
import { Placeholder } from '~/components/placeholders/Placeholder'; import { Placeholder } from "~/components/placeholders/Placeholder";
import { getURL, getPrettyDate } from '~/utils/dom'; import { getURL, getPrettyDate } from "~/utils/dom";
import { PRESETS } from '~/constants/urls'; import { PRESETS } from "~/constants/urls";
import { ProfileTabs } from '../ProfileTabs'; import { ProfileTabs } from "../ProfileTabs";
import { MessageForm } from '~/components/profile/MessageForm'; import { MessageForm } from "~/components/profile/MessageForm";
import { ProfileAvatar } from "../ProfileAvatar";
interface IProps { interface IProps {
user?: IUser; user?: IUser;
@ -19,24 +20,21 @@ interface IProps {
} }
const TAB_HEADERS = { const TAB_HEADERS = {
messages: <MessageForm is_sending_message={false} />, messages: <MessageForm is_sending_message={false} />
}; };
const ProfileInfo: FC<IProps> = ({ user, tab, is_loading, is_own, setTab }) => ( const ProfileInfo: FC<IProps> = ({ user, tab, is_loading, is_own, setTab }) => (
<div> <div>
<Group className={styles.wrap} horizontal> <Group className={styles.wrap} horizontal>
<div <ProfileAvatar />
className={styles.avatar}
style={{
backgroundImage: is_loading
? null
: `url("${user && getURL(user.photo, PRESETS.avatar)}")`,
}}
/>
<div className={styles.field}> <div className={styles.field}>
<div className={styles.name}> <div className={styles.name}>
{is_loading ? <Placeholder width="80%" /> : user.fullname || user.username} {is_loading ? (
<Placeholder width="80%" />
) : (
user.fullname || user.username
)}
</div> </div>
<div className={styles.description}> <div className={styles.description}>

View file

@ -1,12 +1,17 @@
import { api, errorMiddleware, resultMiddleware, configWithToken } from '~/utils/api'; import {
import { API } from '~/constants/api'; api,
import { IResultWithStatus, IMessage } from '~/redux/types'; errorMiddleware,
import { userLoginTransform } from '~/redux/auth/transforms'; resultMiddleware,
import { IUser } from './types'; 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 = ({ export const apiUserLogin = ({
username, username,
password, password
}: { }: {
username: string; username: string;
password: string; password: string;
@ -17,7 +22,9 @@ export const apiUserLogin = ({
.catch(errorMiddleware) .catch(errorMiddleware)
.then(userLoginTransform); .then(userLoginTransform);
export const apiAuthGetUser = ({ access }): Promise<IResultWithStatus<{ user: IUser }>> => export const apiAuthGetUser = ({
access
}): Promise<IResultWithStatus<{ user: IUser }>> =>
api api
.get(API.USER.ME, configWithToken(access)) .get(API.USER.ME, configWithToken(access))
.then(resultMiddleware) .then(resultMiddleware)
@ -25,7 +32,7 @@ export const apiAuthGetUser = ({ access }): Promise<IResultWithStatus<{ user: IU
export const apiAuthGetUserProfile = ({ export const apiAuthGetUserProfile = ({
access, access,
username, username
}): Promise<IResultWithStatus<{ user: IUser }>> => }): Promise<IResultWithStatus<{ user: IUser }>> =>
api api
.get(API.USER.PROFILE(username), configWithToken(access)) .get(API.USER.PROFILE(username), configWithToken(access))
@ -34,7 +41,7 @@ export const apiAuthGetUserProfile = ({
export const apiAuthGetUserMessages = ({ export const apiAuthGetUserMessages = ({
access, access,
username, username
}): Promise<IResultWithStatus<{ messages: IMessage[] }>> => }): Promise<IResultWithStatus<{ messages: IMessage[] }>> =>
api api
.get(API.USER.MESSAGES(username), configWithToken(access)) .get(API.USER.MESSAGES(username), configWithToken(access))
@ -44,7 +51,7 @@ export const apiAuthGetUserMessages = ({
export const apiAuthSendMessage = ({ export const apiAuthSendMessage = ({
access, access,
username, username,
message, message
}): Promise<IResultWithStatus<{ message: IMessage }>> => }): Promise<IResultWithStatus<{ message: IMessage }>> =>
api api
.post(API.USER.MESSAGE_SEND(username), { message }, configWithToken(access)) .post(API.USER.MESSAGE_SEND(username), { message }, configWithToken(access))
@ -54,14 +61,20 @@ export const apiAuthSendMessage = ({
export const apiAuthGetUpdates = ({ export const apiAuthGetUpdates = ({
access, access,
exclude_dialogs, exclude_dialogs,
last, last
}): Promise<IResultWithStatus<{ message: IMessage }>> => }): Promise<IResultWithStatus<{ message: IMessage }>> =>
api 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) .then(resultMiddleware)
.catch(errorMiddleware); .catch(errorMiddleware);
export const apiUpdateUser = ({ access, user }): Promise<IResultWithStatus<{ user: IUser }>> => export const apiUpdateUser = ({
access,
user
}): Promise<IResultWithStatus<{ user: IUser }>> =>
api api
.patch(API.USER.ME, { user }, configWithToken(access)) .patch(API.USER.ME, { user }, configWithToken(access))
.then(resultMiddleware) .then(resultMiddleware)

View file

@ -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 { import {
AUTH_USER_ACTIONS, AUTH_USER_ACTIONS,
EMPTY_USER, EMPTY_USER,
@ -92,6 +99,7 @@ function* sendLoginRequestSaga({
} }
function* refreshUser() { function* refreshUser() {
try {
const { const {
error, error,
data: { user } data: { user }
@ -112,6 +120,11 @@ function* refreshUser() {
} }
yield put(authSetUser({ ...user, is_user: true })); yield put(authSetUser({ ...user, is_user: true }));
} catch (e) {
console.log("erro", e);
} finally {
console.log("ended");
}
} }
function* checkUserSaga({ key }: RehydrateAction) { function* checkUserSaga({ key }: RehydrateAction) {
@ -315,12 +328,14 @@ function* patchUser({ user }: ReturnType<typeof authPatchUser>) {
return yield put(authSetProfile({ patch_errors: data.errors })); 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" })); yield put(authSetProfile({ user: { ...me, ...data.user }, tab: "profile" }));
} }
function* authSaga() { function* authSaga() {
yield takeLatest(REHYDRATE, checkUserSaga); yield takeEvery(REHYDRATE, checkUserSaga);
yield takeLatest([REHYDRATE, AUTH_USER_ACTIONS.LOGGED_IN], startPollingSaga); yield takeLatest([REHYDRATE, AUTH_USER_ACTIONS.LOGGED_IN], startPollingSaga);
yield takeLatest(AUTH_USER_ACTIONS.LOGOUT, logoutSaga); yield takeLatest(AUTH_USER_ACTIONS.LOGOUT, logoutSaga);