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

user menu

This commit is contained in:
Fedor Katurov 2019-11-05 17:14:47 +07:00
parent aff052e66d
commit 8a37fa1f1b
7 changed files with 135 additions and 62 deletions

View file

@ -9,11 +9,10 @@ import { Filler } from '~/components/containers/Filler';
import { selectUser } from '~/redux/auth/selectors'; import { selectUser } from '~/redux/auth/selectors';
import { Group } from '~/components/containers/Group'; import { Group } from '~/components/containers/Group';
import * as MODAL_ACTIONS from '~/redux/modal/actions'; import * as MODAL_ACTIONS from '~/redux/modal/actions';
import * as AUTH_ACTIONS from '~/redux/auth/actions';
import { DIALOGS } from '~/redux/modal/constants'; import { DIALOGS } from '~/redux/modal/constants';
import { pick } from 'ramda'; import { pick } from 'ramda';
import { Icon } from '~/components/input/Icon'; import { UserButton } from '../UserButton';
import { getURL } from '~/utils/dom';
import { API } from '~/constants/api';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
user: pick(['username', 'is_user', 'photo'])(selectUser(state)), user: pick(['username', 'is_user', 'photo'])(selectUser(state)),
@ -22,11 +21,13 @@ const mapStateToProps = state => ({
const mapDispatchToProps = { const mapDispatchToProps = {
push: historyPush, push: historyPush,
showDialog: MODAL_ACTIONS.modalShowDialog, showDialog: MODAL_ACTIONS.modalShowDialog,
authLogout: AUTH_ACTIONS.authLogout,
}; };
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {}; type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const HeaderUnconnected: FC<IProps> = memo(({ user: { username, is_user, photo }, showDialog }) => { const HeaderUnconnected: FC<IProps> = memo(
({ user, user: { username, is_user, photo }, showDialog, authLogout }) => {
const onLogin = useCallback(() => showDialog(DIALOGS.LOGIN), [showDialog]); const onLogin = useCallback(() => showDialog(DIALOGS.LOGIN), [showDialog]);
return ( return (
@ -39,14 +40,7 @@ const HeaderUnconnected: FC<IProps> = memo(({ user: { username, is_user, photo }
<Link to="/">flow</Link> <Link to="/">flow</Link>
</div> </div>
{is_user && ( {is_user && <UserButton user={user} onLogout={authLogout} />}
<Group horizontal className={style.user_button}>
<div>{username}</div>
<div className={style.user_avatar} style={{ backgroundImage: `url('${getURL(photo)}')` }}>
{(!photo || !photo.id) && <Icon icon="profile" />}
</div>
</Group>
)}
{!is_user && ( {!is_user && (
<Group horizontal className={style.user_button} onClick={onLogin}> <Group horizontal className={style.user_button} onClick={onLogin}>
@ -55,7 +49,8 @@ const HeaderUnconnected: FC<IProps> = memo(({ user: { username, is_user, photo }
)} )}
</div> </div>
); );
}); }
);
const Header = connect( const Header = connect(
mapStateToProps, mapStateToProps,

View file

@ -4,6 +4,7 @@
justify-content: flex-end; justify-content: flex-end;
font-weight: 500; font-weight: 500;
height: 120px; height: 120px;
z-index: 5;
} }
.spacer { .spacer {
@ -63,38 +64,7 @@
} }
.user_button { .user_button {
align-items: center; flex: 0;
border-radius: $input_radius; padding-left: $gap / 2;
font: $font_16_semibold;
text-transform: uppercase;
flex: 0 !important;
cursor: pointer; cursor: pointer;
margin-left: $gap;
white-space: nowrap;
box-shadow: inset transparentize($content_bg, 0.8) 0 0 0 1px;
background: transparentize($content_bg, 0.1);
padding: 0 0 0 $gap;
}
.user_avatar {
@include outer_shadow();
flex: 0 0 32px;
width: 32px;
height: 32px;
background: white;
border-radius: $radius;
margin-left: ($gap + 2px) !important;
background: 50% 50% no-repeat $wisegreen;
display: flex;
align-items: center;
justify-content: center;
background-size: cover;
svg {
fill: #222222;
stroke: #222222;
width: 24px;
height: 24px;
}
} }

View file

@ -0,0 +1,28 @@
import React, { FC } from 'react';
import { Group } from '~/components/containers/Group';
import styles from './styles.scss';
import { getURL } from '~/utils/dom';
import { Icon } from '~/components/input/Icon';
import { IUser } from '~/redux/auth/types';
interface IProps {
user: Partial<IUser>;
onLogout: () => void;
}
const UserButton: FC<IProps> = ({ user: { username, photo }, onLogout }) => (
<div className={styles.wrap}>
<Group horizontal className={styles.user_button}>
<div>{username}</div>
<div className={styles.user_avatar} style={{ backgroundImage: `url('${getURL(photo)}')` }}>
{(!photo || !photo.id) && <Icon icon="profile" />}
</div>
</Group>
<div className={styles.menu}>
<div onClick={onLogout}>Выдох</div>
</div>
</div>
);
export { UserButton };

View file

@ -0,0 +1,68 @@
.wrap {
position: relative;
z-index: 2;
&:hover .menu {
display: flex;
}
}
.menu {
position: absolute;
right: 0;
top: 100%;
padding: $gap;
background: $content_bg;
border-radius: 0 0 $radius $radius;
display: none;
z-index: 1;
background: $content_bg;
min-width: 100%;
& > div {
padding: $gap $gap * 2;
cursor: pointer;
opacity: 0.5;
}
&:hover > div {
opacity: 1;
}
}
.user_button {
align-items: center;
border-radius: $input_radius;
font: $font_16_semibold;
text-transform: uppercase;
flex: 0 !important;
cursor: pointer;
margin-left: $gap;
white-space: nowrap;
box-shadow: inset transparentize($content_bg, 0.8) 0 0 0 1px;
background: transparentize($content_bg, 0.1);
padding: 0 0 0 $gap;
}
.user_avatar {
@include outer_shadow();
flex: 0 0 32px;
width: 32px;
height: 32px;
background: white;
border-radius: $radius;
margin-left: ($gap + 2px) !important;
background: 50% 50% no-repeat $wisegreen;
display: flex;
align-items: center;
justify-content: center;
background-size: cover;
svg {
fill: #222222;
stroke: #222222;
width: 24px;
height: 24px;
}
}

View file

@ -28,3 +28,7 @@ export const authSetUser = (profile: Partial<IUser>) => ({
type: AUTH_USER_ACTIONS.SET_USER, type: AUTH_USER_ACTIONS.SET_USER,
profile, profile,
}); });
export const authLogout = () => ({
type: AUTH_USER_ACTIONS.LOGOUT,
});

View file

@ -6,6 +6,8 @@ export const AUTH_USER_ACTIONS = {
SET_USER: 'SET_USER', SET_USER: 'SET_USER',
SET_TOKEN: 'SET_TOKEN', SET_TOKEN: 'SET_TOKEN',
LOGOUT: 'LOGOUT',
GOT_POST_MESSAGE: 'GOT_POST_MESSAGE', GOT_POST_MESSAGE: 'GOT_POST_MESSAGE',
}; };

View file

@ -85,8 +85,14 @@ function* gotPostMessageSaga({ token }: ReturnType<typeof gotPostMessage>) {
if (is_shown && dialog === DIALOGS.LOGIN) yield put(modalSetShown(false)); if (is_shown && dialog === DIALOGS.LOGIN) yield put(modalSetShown(false));
} }
function* logoutSaga() {
yield put(authSetToken(null));
yield put(authSetUser({ ...EMPTY_USER }));
}
function* authSaga() { function* authSaga() {
yield takeLatest(REHYDRATE, checkUserSaga); yield takeLatest(REHYDRATE, checkUserSaga);
yield takeLatest(AUTH_USER_ACTIONS.LOGOUT, logoutSaga);
yield takeLatest(AUTH_USER_ACTIONS.SEND_LOGIN_REQUEST, sendLoginRequestSaga); yield takeLatest(AUTH_USER_ACTIONS.SEND_LOGIN_REQUEST, sendLoginRequestSaga);
yield takeLatest(AUTH_USER_ACTIONS.GOT_POST_MESSAGE, gotPostMessageSaga); yield takeLatest(AUTH_USER_ACTIONS.GOT_POST_MESSAGE, gotPostMessageSaga);
} }