mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
profile layout
This commit is contained in:
parent
0c3625cd18
commit
fe6f977cf1
12 changed files with 236 additions and 13 deletions
|
@ -30,6 +30,7 @@ export const FlowGrid: FC<IProps> = ({
|
||||||
<div className={styles.hero}>
|
<div className={styles.hero}>
|
||||||
<FlowHero heroes={heroes} />
|
<FlowHero heroes={heroes} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.stamp}>
|
<div className={styles.stamp}>
|
||||||
<FlowRecent recent={recent} updated={updated} />
|
<FlowRecent recent={recent} updated={updated} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const URLS = {
|
||||||
},
|
},
|
||||||
NODE_URL: (id: number | string) => `/post${id}`,
|
NODE_URL: (id: number | string) => `/post${id}`,
|
||||||
PROFILE: (username: string) => `/~${username}`,
|
PROFILE: (username: string) => `/~${username}`,
|
||||||
PROFILE_PAGE: `/profile`,
|
PROFILE_PAGE: (username: string) => `/profile/${username}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PRESETS = {
|
export const PRESETS = {
|
||||||
|
|
|
@ -44,7 +44,7 @@ const Component: FC<IProps> = ({ modal: { is_shown } }) => {
|
||||||
<Route path={URLS.NODE_URL(':id')} component={NodeLayout} />
|
<Route path={URLS.NODE_URL(':id')} component={NodeLayout} />
|
||||||
<Route path={URLS.BORIS} component={BorisLayout} />
|
<Route path={URLS.BORIS} component={BorisLayout} />
|
||||||
<Route path={URLS.ERRORS.NOT_FOUND} component={ErrorNotFound} />
|
<Route path={URLS.ERRORS.NOT_FOUND} component={ErrorNotFound} />
|
||||||
<Route path={URLS.PROFILE_PAGE} component={ProfilePage} />
|
<Route path={URLS.PROFILE_PAGE(':username')} component={ProfilePage} />
|
||||||
|
|
||||||
<Redirect to="/" />
|
<Redirect to="/" />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { FC, useState, useCallback } from 'react';
|
import React, { FC, useCallback } from 'react';
|
||||||
import { BetterScrollDialog } from '../BetterScrollDialog';
|
import { BetterScrollDialog } from '../BetterScrollDialog';
|
||||||
import { ProfileInfo } from '~/containers/profile/ProfileInfo';
|
import { ProfileInfo } from '~/containers/profile/ProfileInfo';
|
||||||
import { IDialogProps } from '~/redux/types';
|
import { IDialogProps } from '~/redux/types';
|
||||||
|
|
|
@ -1,10 +1,53 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC, useEffect } from 'react';
|
||||||
import styles from './styles.scss';
|
import styles from './styles.scss';
|
||||||
|
import { ProfilePageLeft } from '../ProfilePageLeft';
|
||||||
|
import { Switch, Route, RouteComponentProps } from 'react-router';
|
||||||
|
import { IState } from '~/redux/store';
|
||||||
|
import { selectAuthProfile } from '~/redux/auth/selectors';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
||||||
|
|
||||||
interface IProps {}
|
const mapStateToProps = (state: IState) => ({
|
||||||
|
profile: selectAuthProfile(state),
|
||||||
|
});
|
||||||
|
|
||||||
const ProfilePage: FC<IProps> = ({}) => {
|
const mapDispatchToProps = {
|
||||||
return <div className={styles.wrap}>PROFILE</div>;
|
authLoadProfile: AUTH_ACTIONS.authLoadProfile,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Props = ReturnType<typeof mapStateToProps> &
|
||||||
|
typeof mapDispatchToProps &
|
||||||
|
RouteComponentProps<{ username: string }> & {};
|
||||||
|
|
||||||
|
const ProfilePageUnconnected: FC<Props> = ({
|
||||||
|
profile,
|
||||||
|
authLoadProfile,
|
||||||
|
match: {
|
||||||
|
params: { username },
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
useEffect(() => {
|
||||||
|
authLoadProfile(username);
|
||||||
|
}, [username]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrap}>
|
||||||
|
<div className={styles.left}>
|
||||||
|
<ProfilePageLeft profile={profile} username={username} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.right}>
|
||||||
|
<Switch>
|
||||||
|
<Route path="/profile/:username" render={() => <div>DEFAULT</div>} />
|
||||||
|
<Route path="/profile/:username/tab" render={() => <div>TAB</div>} />
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProfilePage = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ProfilePageUnconnected);
|
||||||
|
|
||||||
export { ProfilePage };
|
export { ProfilePage };
|
||||||
|
|
|
@ -2,6 +2,19 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background: $content_bg;
|
background: $content_bg;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
justify-content: center;
|
justify-content: stretch;
|
||||||
|
box-shadow: $node_shadow;
|
||||||
|
border-radius: $radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
flex: 1;
|
||||||
|
background: darken($content_bg, 2%);
|
||||||
|
border-radius: $radius 0 0 $radius;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
flex: 3;
|
||||||
}
|
}
|
||||||
|
|
58
src/containers/profile/ProfilePageLeft/index.tsx
Normal file
58
src/containers/profile/ProfilePageLeft/index.tsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import React, { FC, useMemo } from 'react';
|
||||||
|
import styles from './styles.scss';
|
||||||
|
import { IAuthState } from '~/redux/auth/types';
|
||||||
|
import { getURL } from '~/utils/dom';
|
||||||
|
import { PRESETS, URLS } from '~/constants/urls';
|
||||||
|
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Icon } from '~/components/input/Icon';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
profile: IAuthState['profile'];
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfilePageLeft: FC<IProps> = ({ username, profile }) => {
|
||||||
|
const thumb = useMemo(() => {
|
||||||
|
if (!profile || !profile.user || !profile.user.photo) return '';
|
||||||
|
|
||||||
|
return getURL(profile.user.photo, PRESETS.small_hero);
|
||||||
|
}, [profile]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrap}>
|
||||||
|
<div className={styles.avatar} style={{ backgroundImage: `url('${thumb}')` }} />
|
||||||
|
|
||||||
|
<div className={styles.region_wrap}>
|
||||||
|
<div className={styles.region}>
|
||||||
|
<div className={styles.name}>
|
||||||
|
{profile.is_loading ? <Placeholder /> : profile.user.fullname}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.username}>
|
||||||
|
{profile.is_loading ? <Placeholder /> : `~${profile.user.username}`}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.menu}>
|
||||||
|
<Link to={`${URLS.PROFILE_PAGE(username)}/`}>
|
||||||
|
<Icon icon="profile" size={20} />
|
||||||
|
Профиль
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link to={`${URLS.PROFILE_PAGE(username)}/settings`}>
|
||||||
|
<Icon icon="settings" size={20} />
|
||||||
|
Настройки
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link to={`${URLS.PROFILE_PAGE(username)}/messages`}>
|
||||||
|
<Icon icon="messages" size={20} />
|
||||||
|
Сообщения
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ProfilePageLeft };
|
79
src/containers/profile/ProfilePageLeft/styles.scss
Normal file
79
src/containers/profile/ProfilePageLeft/styles.scss
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
.wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 50%;
|
||||||
|
border-radius: $radius 0 0 0;
|
||||||
|
background: 50% 50% no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region_wrap {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 10px;
|
||||||
|
position: relative;
|
||||||
|
top: -$radius;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region {
|
||||||
|
background: $content_bg;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: $radius;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.3) 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font: $font_24_semibold;
|
||||||
|
color: white;
|
||||||
|
padding: $gap $gap 0 $gap;
|
||||||
|
text-transform: uppercase;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
font: $font_14_semibold;
|
||||||
|
padding: 0 $gap $gap $gap;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
color: transparentize(white, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
padding: $gap 0 $gap 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
a {
|
||||||
|
width: 100%;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font: $font_18_semibold;
|
||||||
|
padding: $gap $gap;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
opacity: 0.5;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: opacity 0.25s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-right: $gap;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,6 +44,11 @@ export const authOpenProfile = (username: string, tab?: IAuthState['profile']['t
|
||||||
tab,
|
tab,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const authLoadProfile = (username: string) => ({
|
||||||
|
type: AUTH_USER_ACTIONS.LOAD_PROFILE,
|
||||||
|
username,
|
||||||
|
});
|
||||||
|
|
||||||
export const authSetProfile = (profile: Partial<IAuthState['profile']>) => ({
|
export const authSetProfile = (profile: Partial<IAuthState['profile']>) => ({
|
||||||
type: AUTH_USER_ACTIONS.SET_PROFILE,
|
type: AUTH_USER_ACTIONS.SET_PROFILE,
|
||||||
profile,
|
profile,
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const AUTH_USER_ACTIONS = {
|
||||||
|
|
||||||
GOT_AUTH_POST_MESSAGE: 'GOT_POST_MESSAGE',
|
GOT_AUTH_POST_MESSAGE: 'GOT_POST_MESSAGE',
|
||||||
OPEN_PROFILE: 'OPEN_PROFILE',
|
OPEN_PROFILE: 'OPEN_PROFILE',
|
||||||
|
LOAD_PROFILE: 'LOAD_PROFILE',
|
||||||
SET_PROFILE: 'SET_PROFILE',
|
SET_PROFILE: 'SET_PROFILE',
|
||||||
GET_MESSAGES: 'GET_MESSAGES',
|
GET_MESSAGES: 'GET_MESSAGES',
|
||||||
SEND_MESSAGE: 'SEND_MESSAGE',
|
SEND_MESSAGE: 'SEND_MESSAGE',
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
authSetRestore,
|
authSetRestore,
|
||||||
authRequestRestoreCode,
|
authRequestRestoreCode,
|
||||||
authRestorePassword,
|
authRestorePassword,
|
||||||
|
authLoadProfile,
|
||||||
} from '~/redux/auth/actions';
|
} from '~/redux/auth/actions';
|
||||||
import {
|
import {
|
||||||
apiUserLogin,
|
apiUserLogin,
|
||||||
|
@ -127,9 +128,8 @@ function* logoutSaga() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function* openProfile({ username, tab = 'profile' }: ReturnType<typeof authOpenProfile>) {
|
function* loadProfile({ username }: ReturnType<typeof authLoadProfile>) {
|
||||||
yield put(modalShowDialog(DIALOGS.PROFILE));
|
yield put(authSetProfile({ is_loading: true }));
|
||||||
yield put(authSetProfile({ is_loading: true, tab }));
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
error,
|
error,
|
||||||
|
@ -137,10 +137,22 @@ function* openProfile({ username, tab = 'profile' }: ReturnType<typeof authOpenP
|
||||||
} = yield call(reqWrapper, apiAuthGetUserProfile, { username });
|
} = yield call(reqWrapper, apiAuthGetUserProfile, { username });
|
||||||
|
|
||||||
if (error || !user) {
|
if (error || !user) {
|
||||||
return yield put(modalSetShown(false));
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield put(authSetProfile({ is_loading: false, user, messages: [] }));
|
yield put(authSetProfile({ is_loading: false, user, messages: [] }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function* openProfile({ username, tab = 'profile' }: ReturnType<typeof authOpenProfile>) {
|
||||||
|
yield put(modalShowDialog(DIALOGS.PROFILE));
|
||||||
|
yield put(authSetProfile({ tab }));
|
||||||
|
|
||||||
|
const success: boolean = yield call(loadProfile, authLoadProfile(username));
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return yield put(modalSetShown(false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* getMessages({ username }: ReturnType<typeof authGetMessages>) {
|
function* getMessages({ username }: ReturnType<typeof authGetMessages>) {
|
||||||
|
@ -354,6 +366,7 @@ function* authSaga() {
|
||||||
yield takeLatest(AUTH_USER_ACTIONS.SEND_LOGIN_REQUEST, sendLoginRequestSaga);
|
yield takeLatest(AUTH_USER_ACTIONS.SEND_LOGIN_REQUEST, sendLoginRequestSaga);
|
||||||
yield takeLatest(AUTH_USER_ACTIONS.GOT_AUTH_POST_MESSAGE, gotPostMessageSaga);
|
yield takeLatest(AUTH_USER_ACTIONS.GOT_AUTH_POST_MESSAGE, gotPostMessageSaga);
|
||||||
yield takeLatest(AUTH_USER_ACTIONS.OPEN_PROFILE, openProfile);
|
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.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);
|
||||||
|
|
|
@ -207,6 +207,16 @@ const Sprites: FC<{}> = () => (
|
||||||
<path d="M14 12c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-9c-4.97 0-9 4.03-9 9H0l4 4 4-4H5c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.51 0-2.91-.49-4.06-1.3l-1.42 1.44C8.04 20.3 9.94 21 12 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z" />
|
<path d="M14 12c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-9c-4.97 0-9 4.03-9 9H0l4 4 4-4H5c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.51 0-2.91-.49-4.06-1.3l-1.42 1.44C8.04 20.3 9.94 21 12 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
|
<g id="settings" stroke="none">
|
||||||
|
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||||
|
<path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="messages" stroke="none">
|
||||||
|
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||||
|
<path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z" />
|
||||||
|
</g>
|
||||||
|
|
||||||
<g id="youtube" stroke="none">
|
<g id="youtube" stroke="none">
|
||||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||||
<g transform="scale(0.1) translate(-30 -30)">
|
<g transform="scale(0.1) translate(-30 -30)">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue