mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
messages form
This commit is contained in:
parent
e45dee3c9f
commit
98c66dec8c
24 changed files with 456 additions and 42 deletions
|
@ -22,8 +22,6 @@ const BetterScrollDialog: FC<IProps> = ({
|
|||
footer,
|
||||
width = 600,
|
||||
error,
|
||||
onOverlayClick,
|
||||
onRefCapture,
|
||||
onClose,
|
||||
}) => {
|
||||
const ref = useRef(null);
|
||||
|
@ -43,7 +41,12 @@ const BetterScrollDialog: FC<IProps> = ({
|
|||
</div>
|
||||
)}
|
||||
{header && <div className={styles.header}>{header}</div>}
|
||||
<div className={styles.body}>{children}</div>
|
||||
|
||||
<div className={styles.body}>
|
||||
{children}
|
||||
{error && <div className={styles.error}>{error}</div>}
|
||||
</div>
|
||||
|
||||
{footer && <div className={styles.header}>{footer}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -39,13 +39,14 @@
|
|||
.footer {
|
||||
@include outer_shadow();
|
||||
|
||||
padding: 10px;
|
||||
// padding: 10px;
|
||||
background: darken($content_bg, 2%);
|
||||
}
|
||||
|
||||
.body {
|
||||
@include outer_shadow();
|
||||
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
background: $content_bg;
|
||||
|
@ -86,3 +87,13 @@
|
|||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
pointer-events: none;
|
||||
background: linear-gradient($red, transparentize($red, 1));
|
||||
}
|
||||
|
|
|
@ -1,27 +1,32 @@
|
|||
import React, { FC } from 'react';
|
||||
import React, { FC, useState } 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';
|
||||
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
||||
import { CommentForm } from '~/components/node/CommentForm';
|
||||
import { ProfileMessages } from '~/containers/profile/ProfileMessages';
|
||||
|
||||
const TAB_CONTENT = {
|
||||
profile: <div>PROFILE</div>,
|
||||
messages: <ProfileMessages />,
|
||||
};
|
||||
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}
|
||||
>
|
||||
<ProfileMessages />
|
||||
</BetterScrollDialog>
|
||||
);
|
||||
const ProfileDialogUnconnected: FC<IProps> = ({ onRequestClose, is_loading, user }) => {
|
||||
const [tab, setTab] = useState('messages');
|
||||
|
||||
return (
|
||||
<BetterScrollDialog
|
||||
header={<ProfileInfo is_loading={is_loading} user={user} tab={tab} setTab={setTab} />}
|
||||
onClose={onRequestClose}
|
||||
>
|
||||
{TAB_CONTENT[tab] || null}
|
||||
</BetterScrollDialog>
|
||||
);
|
||||
};
|
||||
|
||||
const ProfileDialog = connect(
|
||||
mapStateToProps,
|
||||
|
|
|
@ -5,19 +5,32 @@ import { Group } from '~/components/containers/Group';
|
|||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { getURL, getPrettyDate } from '~/utils/dom';
|
||||
import { PRESETS } from '~/constants/urls';
|
||||
import { ProfileTabs } from '../ProfileTabs';
|
||||
import { MessageForm } from '~/components/profile/MessageForm';
|
||||
|
||||
interface IProps {
|
||||
user?: IUser;
|
||||
tab: string;
|
||||
|
||||
is_loading?: boolean;
|
||||
is_own?: boolean;
|
||||
|
||||
setTab?: (tab: string) => void;
|
||||
}
|
||||
|
||||
const ProfileInfo: FC<IProps> = ({ user, is_loading = false }) => (
|
||||
<Group>
|
||||
const TAB_HEADERS = {
|
||||
messages: <MessageForm is_sending_message={false} />,
|
||||
};
|
||||
|
||||
const ProfileInfo: FC<IProps> = ({ user, tab, is_loading, is_own, setTab }) => (
|
||||
<div>
|
||||
<Group className={styles.wrap} horizontal>
|
||||
<div
|
||||
className={styles.avatar}
|
||||
style={{
|
||||
backgroundImage: `url("${user && getURL(user.photo, PRESETS.avatar)}")`,
|
||||
backgroundImage: is_loading
|
||||
? null
|
||||
: `url("${user && getURL(user.photo, PRESETS.avatar)}")`,
|
||||
}}
|
||||
/>
|
||||
|
||||
|
@ -31,7 +44,11 @@ const ProfileInfo: FC<IProps> = ({ user, is_loading = false }) => (
|
|||
</div>
|
||||
</div>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<ProfileTabs tab={tab} is_own={is_own} setTab={setTab} />
|
||||
|
||||
{TAB_HEADERS[tab] || null}
|
||||
</div>
|
||||
);
|
||||
|
||||
export { ProfileInfo };
|
||||
|
|
|
@ -16,13 +16,13 @@
|
|||
background: $content_bg 50% 50% no-repeat/cover;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
left: $gap;
|
||||
}
|
||||
|
||||
.field {
|
||||
padding-left: 110px;
|
||||
padding: $gap 0 0 120px;
|
||||
flex: 1;
|
||||
padding-bottom: 5px;
|
||||
// padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.name {
|
||||
|
|
|
@ -36,9 +36,7 @@ const ProfileLayoutUnconnected: FC<IProps> = ({ history, nodeSetCoverImage }) =>
|
|||
|
||||
return (
|
||||
<Group className={styles.wrap} horizontal>
|
||||
<div className={styles.column}>
|
||||
<ProfileInfo user={user} />
|
||||
</div>
|
||||
<div className={styles.column} />
|
||||
|
||||
<Grid className={styles.content}>
|
||||
<div className={styles.comments}>
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
import React, { FC, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectAuthProfile } from '~/redux/auth/selectors';
|
||||
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
||||
import { selectAuthProfile, selectAuth, selectAuthUser } from '~/redux/auth/selectors';
|
||||
import styles from './styles.scss';
|
||||
import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
||||
import { Message } from '~/components/profile/Message';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import pick from 'ramda/es/pick';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
profile: selectAuthProfile(state),
|
||||
user: pick(['id'], selectAuthUser(state)),
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({ profile: selectAuthProfile(state) });
|
||||
const mapDispatchToProps = {
|
||||
authGetMessages: AUTH_ACTIONS.authGetMessages,
|
||||
};
|
||||
|
||||
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||
|
||||
const ProfileMessagesUnconnected: FC<IProps> = ({ profile, authGetMessages }) => {
|
||||
const ProfileMessagesUnconnected: FC<IProps> = ({ profile, user: { id }, authGetMessages }) => {
|
||||
useEffect(() => {
|
||||
if (profile.is_loading || !profile.user || !profile.user.username) return;
|
||||
|
||||
|
@ -20,11 +26,19 @@ const ProfileMessagesUnconnected: FC<IProps> = ({ profile, authGetMessages }) =>
|
|||
}, [profile.user]);
|
||||
|
||||
return (
|
||||
<div className={styles.messages}>
|
||||
{profile.messages.map(message => (
|
||||
<div key={message.id}>{message.text}</div>
|
||||
))}
|
||||
</div>
|
||||
<Group className={styles.messages}>
|
||||
{profile.messages
|
||||
.filter(message => !!message.text)
|
||||
.map((
|
||||
message // TODO: show files / memo
|
||||
) => (
|
||||
<Message message={message} incoming={id !== message.from.id} key={message.id} />
|
||||
))}
|
||||
|
||||
{!profile.is_loading_messages && profile.messages.length > 0 && (
|
||||
<div className={styles.placeholder}>Когда-нибудь здесь будут еще сообщения</div>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
.messages {
|
||||
padding: $gap;
|
||||
background: $node_bg;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font: $font_12_regular;
|
||||
padding: $gap;
|
||||
text-align: center;
|
||||
}
|
36
src/containers/profile/ProfileTabs/index.tsx
Normal file
36
src/containers/profile/ProfileTabs/index.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import React, { FC } from 'react';
|
||||
import styles from './styles.scss';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface IProps {
|
||||
tab: string;
|
||||
is_own: boolean;
|
||||
setTab: (tab: string) => void;
|
||||
}
|
||||
|
||||
const ProfileTabs: FC<IProps> = ({ tab, is_own, setTab }) => (
|
||||
<div className={styles.wrap}>
|
||||
<div
|
||||
className={classNames(styles.tab, { [styles.active]: tab === 'profile' })}
|
||||
onClick={() => setTab('profile')}
|
||||
>
|
||||
Профиль
|
||||
</div>
|
||||
<div
|
||||
className={classNames(styles.tab, { [styles.active]: tab === 'messages' })}
|
||||
onClick={() => setTab('messages')}
|
||||
>
|
||||
Сообщения
|
||||
</div>
|
||||
{is_own && (
|
||||
<div
|
||||
className={classNames(styles.tab, { [styles.active]: tab === 'settings' })}
|
||||
onClick={() => setTab('settings')}
|
||||
>
|
||||
Настройки
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
export { ProfileTabs };
|
22
src/containers/profile/ProfileTabs/styles.scss
Normal file
22
src/containers/profile/ProfileTabs/styles.scss
Normal file
|
@ -0,0 +1,22 @@
|
|||
.wrap {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
margin: $gap * 2 0 0 0;
|
||||
padding: 0 $gap;
|
||||
}
|
||||
|
||||
.tab {
|
||||
@include outer_shadow();
|
||||
|
||||
padding: $gap;
|
||||
margin-right: $gap;
|
||||
border-radius: $radius $radius 0 0;
|
||||
font: $font_14_semibold;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background: lighten($content_bg, 4%);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue