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:
parent
f25d627b4e
commit
60dffc2353
6 changed files with 209 additions and 60 deletions
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
106
src/containers/profile/ProfileAvatar/index.tsx
Normal file
106
src/containers/profile/ProfileAvatar/index.tsx
Normal 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 };
|
11
src/containers/profile/ProfileAvatar/styles.scss
Normal file
11
src/containers/profile/ProfileAvatar/styles.scss
Normal 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;
|
||||||
|
}
|
|
@ -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}>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue