mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
added initial profile dialog
This commit is contained in:
parent
f6baedc4cd
commit
618c2e3275
28 changed files with 315 additions and 58 deletions
|
@ -5,6 +5,7 @@ import { getURL } from '~/utils/dom';
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from '~/components/input/Icon';
|
||||||
import { IUser } from '~/redux/auth/types';
|
import { IUser } from '~/redux/auth/types';
|
||||||
import { PRESETS } from '~/constants/urls';
|
import { PRESETS } from '~/constants/urls';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
user: Partial<IUser>;
|
user: Partial<IUser>;
|
||||||
|
@ -14,7 +15,8 @@ interface IProps {
|
||||||
const UserButton: FC<IProps> = ({ user: { username, photo }, onLogout }) => (
|
const UserButton: FC<IProps> = ({ user: { username, photo }, onLogout }) => (
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<Group horizontal className={styles.user_button}>
|
<Group horizontal className={styles.user_button}>
|
||||||
<div>{username}</div>
|
<Link to={`/~${username}`}>{username}</Link>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={styles.user_avatar}
|
className={styles.user_avatar}
|
||||||
style={{ backgroundImage: `url('${getURL(photo, PRESETS.avatar)}')` }}
|
style={{ backgroundImage: `url('${getURL(photo, PRESETS.avatar)}')` }}
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #cccccc;
|
color: #cccccc;
|
||||||
word-break: break-all;
|
word-break: break-word;
|
||||||
|
|
||||||
b {
|
b {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
|
@ -6,21 +6,21 @@ import { Group } from '~/components/containers/Group';
|
||||||
const ParagraphPlaceholder = ({}) => (
|
const ParagraphPlaceholder = ({}) => (
|
||||||
<Group>
|
<Group>
|
||||||
<div className={styles.para}>
|
<div className={styles.para}>
|
||||||
<Placeholder width={120} />
|
<Placeholder width="120px" />
|
||||||
<Placeholder width={60} />
|
<Placeholder width="60px" />
|
||||||
<Placeholder width={30} />
|
<Placeholder width="30px" />
|
||||||
<Placeholder width={70} />
|
<Placeholder width="70px" />
|
||||||
<Placeholder width={160} />
|
<Placeholder width="160px" />
|
||||||
<Placeholder width={30} />
|
<Placeholder width="30px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.para}>
|
<div className={styles.para}>
|
||||||
<Placeholder width={40} />
|
<Placeholder width="40px" />
|
||||||
<Placeholder width={30} />
|
<Placeholder width="30px" />
|
||||||
<Placeholder width={120} />
|
<Placeholder width="120px" />
|
||||||
<Placeholder width={70} />
|
<Placeholder width="70px" />
|
||||||
<Placeholder width={160} />
|
<Placeholder width="160px" />
|
||||||
<Placeholder width={30} />
|
<Placeholder width="30px" />
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,16 +2,13 @@ import React, { FC } from 'react';
|
||||||
import * as styles from './styles.scss';
|
import * as styles from './styles.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
width?: number;
|
width?: string;
|
||||||
height?: number;
|
height?: number;
|
||||||
color?: string;
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Placeholder: FC<IProps> = ({ width = 120, height, color }) => (
|
const Placeholder: FC<IProps> = ({ width = '120px', height, color }) => (
|
||||||
<div
|
<div className={styles.placeholder} style={{ height, color, width }} />
|
||||||
className={styles.placeholder}
|
|
||||||
style={{ height, color, width }}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export { Placeholder };
|
export { Placeholder };
|
||||||
|
|
|
@ -5,8 +5,9 @@ export const API = {
|
||||||
USER: {
|
USER: {
|
||||||
LOGIN: '/auth/login',
|
LOGIN: '/auth/login',
|
||||||
VKONTAKTE_LOGIN: `${process.env.API_HOST}/auth/vkontakte`,
|
VKONTAKTE_LOGIN: `${process.env.API_HOST}/auth/vkontakte`,
|
||||||
ME: '/auth/', //
|
ME: '/auth/',
|
||||||
UPLOAD: (target, type) => `/upload/${target}/${type}`,
|
UPLOAD: (target, type) => `/upload/${target}/${type}`,
|
||||||
|
PROFILE: (username: string) => `/user/${username}`,
|
||||||
},
|
},
|
||||||
NODE: {
|
NODE: {
|
||||||
SAVE: '/node/',
|
SAVE: '/node/',
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const URLS = {
|
||||||
BACKEND_DOWN: '/oopsie',
|
BACKEND_DOWN: '/oopsie',
|
||||||
},
|
},
|
||||||
NODE_URL: (id: number | string) => `/post${id}`,
|
NODE_URL: (id: number | string) => `/post${id}`,
|
||||||
|
PROFILE: (username: string) => `/~${username}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PRESETS = {
|
export const PRESETS = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { FC, useEffect } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { hot } from 'react-hot-loader';
|
import { hot } from 'react-hot-loader';
|
||||||
import { ConnectedRouter } from 'connected-react-router';
|
import { ConnectedRouter } from 'connected-react-router';
|
||||||
|
@ -8,7 +8,6 @@ import { FlowLayout } from '~/containers/flow/FlowLayout';
|
||||||
import { MainLayout } from '~/containers/main/MainLayout';
|
import { MainLayout } from '~/containers/main/MainLayout';
|
||||||
import { ImageExample } from '~/containers/examples/ImageExample';
|
import { ImageExample } from '~/containers/examples/ImageExample';
|
||||||
import { EditorExample } from '~/containers/examples/EditorExample';
|
import { EditorExample } from '~/containers/examples/EditorExample';
|
||||||
import { HorizontalExample } from '~/containers/examples/HorizontalExample';
|
|
||||||
import { Sprites } from '~/sprites/Sprites';
|
import { Sprites } from '~/sprites/Sprites';
|
||||||
import { URLS } from '~/constants/urls';
|
import { URLS } from '~/constants/urls';
|
||||||
import { Modal } from '~/containers/dialogs/Modal';
|
import { Modal } from '~/containers/dialogs/Modal';
|
||||||
|
@ -19,6 +18,7 @@ import { NodeLayout } from './node/NodeLayout';
|
||||||
import { BottomContainer } from '~/containers/main/BottomContainer';
|
import { BottomContainer } from '~/containers/main/BottomContainer';
|
||||||
import { BorisLayout } from './node/BorisLayout';
|
import { BorisLayout } from './node/BorisLayout';
|
||||||
import { ErrorNotFound } from './pages/ErrorNotFound';
|
import { ErrorNotFound } from './pages/ErrorNotFound';
|
||||||
|
import { ProfileLayout } from './profile/ProfileLayout';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
modal: selectModal(state),
|
modal: selectModal(state),
|
||||||
|
|
28
src/containers/dialogs/ProfileDialog/index.tsx
Normal file
28
src/containers/dialogs/ProfileDialog/index.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { BetterScrollDialog } from '../BetterScrollDialog';
|
||||||
|
import styles from './styles.scss';
|
||||||
|
import { ProfileInfo } from '~/containers/profile/ProfileInfo';
|
||||||
|
import { IDialogProps } from '~/redux/types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectAuthProfile } from '~/redux/auth/selectors';
|
||||||
|
|
||||||
|
const mapStateToProps = selectAuthProfile;
|
||||||
|
const mapDispatchToProps = {};
|
||||||
|
|
||||||
|
type IProps = IDialogProps & ReturnType<typeof mapStateToProps> & {};
|
||||||
|
|
||||||
|
const ProfileDialogUnconnected: FC<IProps> = ({ onRequestClose, is_loading, user }) => (
|
||||||
|
<BetterScrollDialog
|
||||||
|
header={<ProfileInfo is_loading={is_loading} user={user} />}
|
||||||
|
onClose={onRequestClose}
|
||||||
|
>
|
||||||
|
<div className={styles.example} />
|
||||||
|
</BetterScrollDialog>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ProfileDialog = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ProfileDialogUnconnected);
|
||||||
|
|
||||||
|
export { ProfileDialog };
|
0
src/containers/dialogs/ProfileDialog/styles.scss
Normal file
0
src/containers/dialogs/ProfileDialog/styles.scss
Normal file
|
@ -9,6 +9,7 @@ import styles from './styles.scss';
|
||||||
import { CommentForm } from '~/components/node/CommentForm';
|
import { CommentForm } from '~/components/node/CommentForm';
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from '~/components/containers/Group';
|
||||||
import boris from '~/sprites/boris_robot.svg';
|
import boris from '~/sprites/boris_robot.svg';
|
||||||
|
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
node: selectNode(state),
|
node: selectNode(state),
|
||||||
|
@ -78,7 +79,11 @@ const BorisLayoutUnconnected: FC<IProps> = ({
|
||||||
<Group className={styles.content}>
|
<Group className={styles.content}>
|
||||||
{is_user && <CommentForm id={0} />}
|
{is_user && <CommentForm id={0} />}
|
||||||
|
|
||||||
|
{is_loading_comments ? (
|
||||||
|
<NodeNoComments is_loading />
|
||||||
|
) : (
|
||||||
<NodeComments comments={comments} />
|
<NodeComments comments={comments} />
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
padding: $gap;
|
padding: $gap;
|
||||||
background: $content_bg;
|
background: $content_bg;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
|
flex: 0 1 $limited_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
|
@ -70,12 +71,13 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
width: 80%;
|
flex: 0 1 $limited_width;
|
||||||
margin: auto;
|
|
||||||
|
|
||||||
@include tablet {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
// margin: auto;
|
||||||
|
|
||||||
|
// @include tablet {
|
||||||
|
// width: 100%;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
|
@ -101,9 +103,10 @@
|
||||||
|
|
||||||
.caption {
|
.caption {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 50%;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 50%;
|
width: 100%;
|
||||||
|
max-width: $limited_width;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
@ -114,6 +117,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-bottom: $gap * 2;
|
padding-bottom: $gap * 2;
|
||||||
padding: 0 10% $gap * 2;
|
padding: 0 10% $gap * 2;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
|
||||||
@include tablet {
|
@include tablet {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
27
src/containers/profile/ProfileInfo/index.tsx
Normal file
27
src/containers/profile/ProfileInfo/index.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { IUser } from '~/redux/auth/types';
|
||||||
|
import styles from './styles.scss';
|
||||||
|
import { Grid } from '~/components/containers/Grid';
|
||||||
|
import { Group } from '~/components/containers/Group';
|
||||||
|
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
user?: IUser;
|
||||||
|
is_loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileInfo: FC<IProps> = ({ user, is_loading = false }) => (
|
||||||
|
<Group className={styles.wrap} horizontal>
|
||||||
|
<div className={styles.avatar} />
|
||||||
|
|
||||||
|
<Group className={styles.field}>
|
||||||
|
<div className={styles.name}>{is_loading ? <Placeholder width="80%" /> : 'User Name'}</div>
|
||||||
|
|
||||||
|
<div className={styles.desription}>
|
||||||
|
{is_loading ? <Placeholder /> : 'Some description here'}
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { ProfileInfo };
|
28
src/containers/profile/ProfileInfo/styles.scss
Normal file
28
src/containers/profile/ProfileInfo/styles.scss
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
.wrap {
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start !important;
|
||||||
|
// min-height: 64px;
|
||||||
|
padding: $gap;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
@include outer_shadow();
|
||||||
|
|
||||||
|
border-radius: $radius;
|
||||||
|
width: 140px;
|
||||||
|
height: 140px;
|
||||||
|
background: $content_bg;
|
||||||
|
position: absolute;
|
||||||
|
top: -60px;
|
||||||
|
left: $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
padding-left: 140px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font: $font_24_bold;
|
||||||
|
}
|
58
src/containers/profile/ProfileLayout/index.tsx
Normal file
58
src/containers/profile/ProfileLayout/index.tsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import React, { FC, useEffect, useState } from 'react';
|
||||||
|
import { useRouteMatch, withRouter, RouteComponentProps } from 'react-router';
|
||||||
|
import styles from './styles.scss';
|
||||||
|
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
||||||
|
import { Grid } from '~/components/containers/Grid';
|
||||||
|
import { CommentForm } from '~/components/node/CommentForm';
|
||||||
|
import { ProfileInfo } from '../ProfileInfo';
|
||||||
|
import * as NODE_ACTIONS from '~/redux/node/actions';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { IUser } from '~/redux/auth/types';
|
||||||
|
import { Group } from '~/components/containers/Group';
|
||||||
|
|
||||||
|
const mapStateToProps = () => ({});
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
nodeSetCoverImage: NODE_ACTIONS.nodeSetCoverImage,
|
||||||
|
};
|
||||||
|
|
||||||
|
type IProps = RouteComponentProps & typeof mapDispatchToProps & {};
|
||||||
|
|
||||||
|
const ProfileLayoutUnconnected: FC<IProps> = ({ history, nodeSetCoverImage }) => {
|
||||||
|
const {
|
||||||
|
params: { username },
|
||||||
|
} = useRouteMatch<{ username: string }>();
|
||||||
|
const [user, setUser] = useState<IUser>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) setUser(null);
|
||||||
|
}, [username]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user && user.id && user.cover) {
|
||||||
|
nodeSetCoverImage(user.cover);
|
||||||
|
return () => nodeSetCoverImage(null);
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group className={styles.wrap} horizontal>
|
||||||
|
<div className={styles.column}>
|
||||||
|
<ProfileInfo user={user} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Grid className={styles.content}>
|
||||||
|
<div className={styles.comments}>
|
||||||
|
<CommentForm id={0} />
|
||||||
|
<NodeNoComments is_loading={false} />
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProfileLayout = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withRouter(ProfileLayoutUnconnected));
|
||||||
|
|
||||||
|
export { ProfileLayout };
|
27
src/containers/profile/ProfileLayout/styles.scss
Normal file
27
src/containers/profile/ProfileLayout/styles.scss
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
.wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start !important;
|
||||||
|
justify-content: flex-start !important;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 3;
|
||||||
|
// flex: 0 1 $limited_width;
|
||||||
|
// padding: $gap;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments {
|
||||||
|
background: $node_bg;
|
||||||
|
border-radius: $radius;
|
||||||
|
padding: $gap;
|
||||||
|
}
|
|
@ -19,8 +19,8 @@ export const authSetToken = (token: IAuthState['token']) => ({
|
||||||
token,
|
token,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const gotPostMessage = ({ token }: { token: string }) => ({
|
export const gotAuthPostMessage = ({ token }: { token: string }) => ({
|
||||||
type: AUTH_USER_ACTIONS.GOT_POST_MESSAGE,
|
type: AUTH_USER_ACTIONS.GOT_AUTH_POST_MESSAGE,
|
||||||
token,
|
token,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,3 +32,13 @@ export const authSetUser = (profile: Partial<IUser>) => ({
|
||||||
export const authLogout = () => ({
|
export const authLogout = () => ({
|
||||||
type: AUTH_USER_ACTIONS.LOGOUT,
|
type: AUTH_USER_ACTIONS.LOGOUT,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const authOpenProfile = (username: string) => ({
|
||||||
|
type: AUTH_USER_ACTIONS.OPEN_PROFILE,
|
||||||
|
username,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const authSetProfile = (profile: Partial<IAuthState['profile']>) => ({
|
||||||
|
type: AUTH_USER_ACTIONS.SET_PROFILE,
|
||||||
|
profile,
|
||||||
|
});
|
||||||
|
|
|
@ -22,3 +22,12 @@ export const apiAuthGetUser = ({ access }): Promise<IResultWithStatus<{ user: IU
|
||||||
.get(API.USER.ME, configWithToken(access))
|
.get(API.USER.ME, configWithToken(access))
|
||||||
.then(resultMiddleware)
|
.then(resultMiddleware)
|
||||||
.catch(errorMiddleware);
|
.catch(errorMiddleware);
|
||||||
|
|
||||||
|
export const apiAuthGetUserProfile = ({
|
||||||
|
access,
|
||||||
|
username,
|
||||||
|
}): Promise<IResultWithStatus<{ user: IUser }>> =>
|
||||||
|
api
|
||||||
|
.get(API.USER.PROFILE(username), configWithToken(access))
|
||||||
|
.then(resultMiddleware)
|
||||||
|
.catch(errorMiddleware);
|
||||||
|
|
|
@ -8,7 +8,9 @@ export const AUTH_USER_ACTIONS = {
|
||||||
|
|
||||||
LOGOUT: 'LOGOUT',
|
LOGOUT: 'LOGOUT',
|
||||||
|
|
||||||
GOT_POST_MESSAGE: 'GOT_POST_MESSAGE',
|
GOT_AUTH_POST_MESSAGE: 'GOT_POST_MESSAGE',
|
||||||
|
OPEN_PROFILE: 'OPEN_PROFILE',
|
||||||
|
SET_PROFILE: 'SET_PROFILE',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const USER_ERRORS = {
|
export const USER_ERRORS = {
|
||||||
|
|
|
@ -14,23 +14,32 @@ const setLoginError: ActionHandler<typeof ActionCreators.userSetLoginError> = (
|
||||||
login: {
|
login: {
|
||||||
...state.login,
|
...state.login,
|
||||||
error,
|
error,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const setUser: ActionHandler<typeof ActionCreators.authSetUser> = (state, { profile }) => ({
|
const setUser: ActionHandler<typeof ActionCreators.authSetUser> = (state, { profile }) => ({
|
||||||
...state,
|
...state,
|
||||||
user: {
|
user: {
|
||||||
...state.user,
|
...state.user,
|
||||||
...profile
|
...profile,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const setToken: ActionHandler<typeof ActionCreators.authSetToken> = (state, { token }) => ({
|
const setToken: ActionHandler<typeof ActionCreators.authSetToken> = (state, { token }) => ({
|
||||||
...state, token,
|
...state,
|
||||||
|
token,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setProfile: ActionHandler<typeof ActionCreators.authSetProfile> = (state, { profile }) => ({
|
||||||
|
...state,
|
||||||
|
profile: {
|
||||||
|
...state.profile,
|
||||||
|
...profile,
|
||||||
|
},
|
||||||
|
});
|
||||||
export const AUTH_USER_HANDLERS = {
|
export const AUTH_USER_HANDLERS = {
|
||||||
[AUTH_USER_ACTIONS.SET_LOGIN_ERROR]: setLoginError,
|
[AUTH_USER_ACTIONS.SET_LOGIN_ERROR]: setLoginError,
|
||||||
[AUTH_USER_ACTIONS.SET_USER]: setUser,
|
[AUTH_USER_ACTIONS.SET_USER]: setUser,
|
||||||
[AUTH_USER_ACTIONS.SET_TOKEN]: setToken,
|
[AUTH_USER_ACTIONS.SET_TOKEN]: setToken,
|
||||||
|
[AUTH_USER_ACTIONS.SET_PROFILE]: setProfile,
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,10 +10,16 @@ const HANDLERS = {
|
||||||
const INITIAL_STATE: IAuthState = {
|
const INITIAL_STATE: IAuthState = {
|
||||||
token: null,
|
token: null,
|
||||||
user: { ...EMPTY_USER },
|
user: { ...EMPTY_USER },
|
||||||
|
|
||||||
login: {
|
login: {
|
||||||
error: null,
|
error: null,
|
||||||
is_loading: false,
|
is_loading: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
profile: {
|
||||||
|
is_loading: true,
|
||||||
|
user: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createReducer(INITIAL_STATE, HANDLERS);
|
export default createReducer(INITIAL_STATE, HANDLERS);
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
import { call, put, takeLatest, select } from 'redux-saga/effects';
|
import { call, put, takeLatest, select, delay } from 'redux-saga/effects';
|
||||||
import { AUTH_USER_ACTIONS, EMPTY_USER, USER_ERRORS } from '~/redux/auth/constants';
|
import { AUTH_USER_ACTIONS, EMPTY_USER, USER_ERRORS } from '~/redux/auth/constants';
|
||||||
import {
|
import {
|
||||||
authSetToken,
|
authSetToken,
|
||||||
userSetLoginError,
|
userSetLoginError,
|
||||||
authSetUser,
|
authSetUser,
|
||||||
userSendLoginRequest,
|
userSendLoginRequest,
|
||||||
gotPostMessage,
|
gotAuthPostMessage,
|
||||||
|
authOpenProfile,
|
||||||
|
authSetProfile,
|
||||||
} from '~/redux/auth/actions';
|
} from '~/redux/auth/actions';
|
||||||
import { apiUserLogin, apiAuthGetUser } from '~/redux/auth/api';
|
import { apiUserLogin, apiAuthGetUser, apiAuthGetUserProfile } from '~/redux/auth/api';
|
||||||
import { modalSetShown } from '~/redux/modal/actions';
|
import { modalSetShown, modalShowDialog } from '~/redux/modal/actions';
|
||||||
import { selectToken } from './selectors';
|
import { selectToken } from './selectors';
|
||||||
import { IResultWithStatus } from '../types';
|
import { IResultWithStatus } from '../types';
|
||||||
import { IUser } from './types';
|
import { IUser } from './types';
|
||||||
import { REHYDRATE, RehydrateAction } from 'redux-persist';
|
import { REHYDRATE, RehydrateAction } from 'redux-persist';
|
||||||
import path from 'ramda/es/path';
|
|
||||||
import { selectModal } from '../modal/selectors';
|
import { selectModal } from '../modal/selectors';
|
||||||
import { IModalState } from '../modal/reducer';
|
import { IModalState } from '../modal/reducer';
|
||||||
import { DIALOGS } from '../modal/constants';
|
import { DIALOGS } from '../modal/constants';
|
||||||
|
@ -76,7 +77,7 @@ function* checkUserSaga({ key }: RehydrateAction) {
|
||||||
yield call(refreshUser);
|
yield call(refreshUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
function* gotPostMessageSaga({ token }: ReturnType<typeof gotPostMessage>) {
|
function* gotPostMessageSaga({ token }: ReturnType<typeof gotAuthPostMessage>) {
|
||||||
yield put(authSetToken(token));
|
yield put(authSetToken(token));
|
||||||
yield call(refreshUser);
|
yield call(refreshUser);
|
||||||
|
|
||||||
|
@ -90,11 +91,29 @@ function* logoutSaga() {
|
||||||
yield put(authSetUser({ ...EMPTY_USER }));
|
yield put(authSetUser({ ...EMPTY_USER }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function* openProfile({ username }: ReturnType<typeof authOpenProfile>) {
|
||||||
|
yield put(modalShowDialog(DIALOGS.PROFILE));
|
||||||
|
yield put(authSetProfile({ is_loading: true }));
|
||||||
|
|
||||||
|
const {
|
||||||
|
error,
|
||||||
|
data: { user },
|
||||||
|
} = yield call(reqWrapper, apiAuthGetUserProfile, { username });
|
||||||
|
|
||||||
|
if (error || !user) {
|
||||||
|
return yield put(modalSetShown(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
yield put(authSetProfile({ is_loading: false }));
|
||||||
|
console.log({ username, 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.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_AUTH_POST_MESSAGE, gotPostMessageSaga);
|
||||||
|
yield takeLatest(AUTH_USER_ACTIONS.OPEN_PROFILE, openProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default authSaga;
|
export default authSaga;
|
||||||
|
|
|
@ -4,3 +4,4 @@ export const selectAuth = (state: IState): IState['auth'] => state.auth;
|
||||||
export const selectUser = (state: IState): IState['auth']['user'] => state.auth.user;
|
export const selectUser = (state: IState): IState['auth']['user'] => state.auth.user;
|
||||||
export const selectToken = (state: IState): IState['auth']['token'] => state.auth.token;
|
export const selectToken = (state: IState): IState['auth']['token'] => state.auth.token;
|
||||||
export const selectAuthLogin = (state: IState): IState['auth']['login'] => state.auth.login;
|
export const selectAuthLogin = (state: IState): IState['auth']['login'] => state.auth.login;
|
||||||
|
export const selectAuthProfile = (state: IState): IState['auth']['profile'] => state.auth.profile;
|
||||||
|
|
|
@ -26,4 +26,9 @@ export type IAuthState = Readonly<{
|
||||||
error: string;
|
error: string;
|
||||||
is_loading: boolean;
|
is_loading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
profile: {
|
||||||
|
is_loading: boolean;
|
||||||
|
user: IUser;
|
||||||
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { EditorDialogVideo } from '~/containers/editors/EditorDialogVideo';
|
||||||
import { EditorDialogAudio } from '~/containers/editors/EditorDialogAudio';
|
import { EditorDialogAudio } from '~/containers/editors/EditorDialogAudio';
|
||||||
import { NODE_TYPES } from '../node/constants';
|
import { NODE_TYPES } from '../node/constants';
|
||||||
import { TestDialog } from '~/containers/dialogs/TestDialog';
|
import { TestDialog } from '~/containers/dialogs/TestDialog';
|
||||||
|
import { ProfileDialog } from '~/containers/dialogs/ProfileDialog';
|
||||||
|
|
||||||
export const MODAL_ACTIONS = {
|
export const MODAL_ACTIONS = {
|
||||||
SET_SHOWN: 'MODAL.SET_SHOWN',
|
SET_SHOWN: 'MODAL.SET_SHOWN',
|
||||||
|
@ -21,6 +22,7 @@ export const DIALOGS = {
|
||||||
EDITOR_AUDIO: 'EDITOR_AUDIO',
|
EDITOR_AUDIO: 'EDITOR_AUDIO',
|
||||||
LOGIN: 'LOGIN',
|
LOGIN: 'LOGIN',
|
||||||
LOADING: 'LOADING',
|
LOADING: 'LOADING',
|
||||||
|
PROFILE: 'PROFILE',
|
||||||
TEST: 'TEST',
|
TEST: 'TEST',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,6 +34,7 @@ export const DIALOG_CONTENT = {
|
||||||
[DIALOGS.LOGIN]: LoginDialog,
|
[DIALOGS.LOGIN]: LoginDialog,
|
||||||
[DIALOGS.LOADING]: LoadingDialog,
|
[DIALOGS.LOADING]: LoadingDialog,
|
||||||
[DIALOGS.TEST]: TestDialog,
|
[DIALOGS.TEST]: TestDialog,
|
||||||
|
[DIALOGS.PROFILE]: ProfileDialog,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NODE_EDITOR_DIALOGS = {
|
export const NODE_EDITOR_DIALOGS = {
|
||||||
|
|
|
@ -25,7 +25,7 @@ import playerSaga from '~/redux/player/sagas';
|
||||||
import { IAuthState } from '~/redux/auth/types';
|
import { IAuthState } from '~/redux/auth/types';
|
||||||
|
|
||||||
import modalReducer, { IModalState } from '~/redux/modal/reducer';
|
import modalReducer, { IModalState } from '~/redux/modal/reducer';
|
||||||
import { gotPostMessage } from './auth/actions';
|
import { gotAuthPostMessage, authOpenProfile } from './auth/actions';
|
||||||
|
|
||||||
const authPersistConfig: PersistConfig = {
|
const authPersistConfig: PersistConfig = {
|
||||||
key: 'auth',
|
key: 'auth',
|
||||||
|
@ -46,6 +46,13 @@ export interface IState {
|
||||||
export const sagaMiddleware = createSagaMiddleware();
|
export const sagaMiddleware = createSagaMiddleware();
|
||||||
export const history = createBrowserHistory();
|
export const history = createBrowserHistory();
|
||||||
|
|
||||||
|
history.listen(({ pathname }) => {
|
||||||
|
if (pathname.match(/~([\wа-яА-Я]+)/)) {
|
||||||
|
const [, username] = pathname.match(/~([\wа-яА-Я]+)/);
|
||||||
|
window.postMessage({ type: 'username', username }, '*');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const composeEnhancers =
|
const composeEnhancers =
|
||||||
typeof window === 'object' && (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
typeof window === 'object' && (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
||||||
? (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
|
? (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
|
||||||
|
@ -72,9 +79,11 @@ export function configureStore(): { store: Store<IState>; persistor: Persistor }
|
||||||
sagaMiddleware.run(playerSaga);
|
sagaMiddleware.run(playerSaga);
|
||||||
|
|
||||||
window.addEventListener('message', message => {
|
window.addEventListener('message', message => {
|
||||||
if (!message || !message.data || message.data.type !== 'oauth_login' || !message.data.token)
|
if (message && message.data && message.data.type === 'oauth_login' && message.data.token)
|
||||||
return;
|
return store.dispatch(gotAuthPostMessage({ token: message.data.token }));
|
||||||
store.dispatch(gotPostMessage({ token: message.data.token }));
|
|
||||||
|
if (message && message.data && message.data.type === 'username' && message.data.username)
|
||||||
|
return store.dispatch(authOpenProfile(message.data.username));
|
||||||
});
|
});
|
||||||
|
|
||||||
const persistor = persistStore(store);
|
const persistor = persistStore(store);
|
||||||
|
|
|
@ -87,6 +87,15 @@ body {
|
||||||
font: $font_24_bold;
|
font: $font_24_bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.username) {
|
||||||
|
background: transparentize($color: #000000, $amount: 0.8);
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: $wisegreen;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
@ -117,14 +126,6 @@ body {
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ::-webkit-scrollbar-track:hover {
|
|
||||||
// background: #666666;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ::-webkit-scrollbar-track:active {
|
|
||||||
// background: #333333;
|
|
||||||
// }
|
|
||||||
|
|
||||||
::-webkit-scrollbar-corner {
|
::-webkit-scrollbar-corner {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ $dialog_radius: $radius * 2;
|
||||||
|
|
||||||
$input_height: 36px;
|
$input_height: 36px;
|
||||||
$info_height: 24px;
|
$info_height: 24px;
|
||||||
|
$limited_width: 940px;
|
||||||
|
|
||||||
$panel_size: 64px;
|
$panel_size: 64px;
|
||||||
$node_title_height: $panel_size;
|
$node_title_height: $panel_size;
|
||||||
|
|
|
@ -79,6 +79,10 @@ export const formatText = (text: string): string =>
|
||||||
.replace(/(\n{2,})/gi, '\n')
|
.replace(/(\n{2,})/gi, '\n')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
|
.replace(
|
||||||
|
/~([\wа-яА-Я\-]+)/giu,
|
||||||
|
'<span class="username" onClick="window.postMessage({ type: \'username\', username: \'$1\'});">~$1</span>'
|
||||||
|
)
|
||||||
.replace(/:\/\//gim, ':|--|')
|
.replace(/:\/\//gim, ':|--|')
|
||||||
.replace(/(\/\/[^\n]+)/gim, '<span class="grey">$1</span>')
|
.replace(/(\/\/[^\n]+)/gim, '<span class="grey">$1</span>')
|
||||||
.replace(/(\/\*[\s\S]*?\*\/)/gim, '<span class="grey">$1</span>')
|
.replace(/(\/\*[\s\S]*?\*\/)/gim, '<span class="grey">$1</span>')
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue