diff --git a/src/components/profile/ProfileSidebarInfo/index.tsx b/src/components/profile/ProfileSidebarInfo/index.tsx new file mode 100644 index 00000000..35fa630e --- /dev/null +++ b/src/components/profile/ProfileSidebarInfo/index.tsx @@ -0,0 +1,31 @@ +import React, { FC } from 'react'; +import styles from './styles.module.scss'; +import { ProfileAvatar } from '~/containers/profile/ProfileAvatar'; +import { Placeholder } from '~/components/placeholders/Placeholder'; +import { getPrettyDate } from '~/utils/dom'; +import { IUser } from '~/redux/auth/types'; + +interface IProps { + is_loading: boolean; + user: IUser; +} + +const ProfileSidebarInfo: FC = ({ is_loading, user }) => ( +
+
+ +
+ +
+
+ {is_loading ? : user.fullname || user.username} +
+ +
+ {is_loading ? : getPrettyDate(user.last_seen)} +
+
+
+); + +export { ProfileSidebarInfo }; diff --git a/src/components/profile/ProfileSidebarInfo/styles.module.scss b/src/components/profile/ProfileSidebarInfo/styles.module.scss new file mode 100644 index 00000000..d1bd8faf --- /dev/null +++ b/src/components/profile/ProfileSidebarInfo/styles.module.scss @@ -0,0 +1,39 @@ +.wrap.wrap { + justify-content: center; + align-items: center; + box-sizing: border-box; + position: relative; + flex: 0 0 100px; + display: flex; + margin: $gap; + box-shadow: transparentize(white, 0.95) 0 0 0 1px; + border-radius: $radius; + background: transparentize(black, 0.8); +} + +.avatar { + border-radius: $radius; + width: 100px; + height: 100px; + background-size: cover; + position: relative; + flex: 0 0 100px; + left: -$gap; + margin-right: 10px; +} + +.field { + flex: 1; + padding: $gap; +} + +.name { + font: $font_24_bold; + margin-bottom: 4px; +} + +.description { + color: transparentize($color: white, $amount: 0.7); + font: $font_14_regular; + text-transform: lowercase; +} diff --git a/src/components/profile/ProfileSidebarMenu/index.tsx b/src/components/profile/ProfileSidebarMenu/index.tsx new file mode 100644 index 00000000..af89fb41 --- /dev/null +++ b/src/components/profile/ProfileSidebarMenu/index.tsx @@ -0,0 +1,21 @@ +import React, { FC } from 'react'; +import styles from './styles.module.scss'; +import { Icon } from '~/components/input/Icon'; + +interface IProps {} + +const ProfileSidebarMenu: FC = () => ( +
+
+ + Настройки +
+ +
+ + Сообщения +
+
+); + +export { ProfileSidebarMenu }; diff --git a/src/components/profile/ProfileSidebarMenu/styles.module.scss b/src/components/profile/ProfileSidebarMenu/styles.module.scss new file mode 100644 index 00000000..c1541578 --- /dev/null +++ b/src/components/profile/ProfileSidebarMenu/styles.module.scss @@ -0,0 +1,42 @@ +.wrap { + display: flex; + align-items: stretch; + justify-content: center; + flex-direction: column; + margin: 0 $gap; + box-sizing: border-box; + box-shadow: $sidebar_border 0 0 0 1px; + border-radius: $radius; + background-color: transparentize(black, 0.9) +} + +.row { + padding: $gap; + font: $font_18_semibold; + box-shadow: $sidebar_border 0 -1px; + cursor: pointer; + background-color: transparentize(black, 1); + transition: all 250ms; + display: flex; + align-items: center; + justify-content: flex-start; + height: 30px; + opacity: 0.5; + + &:hover { + background-color: transparentize($wisegreen, 0.5); + opacity: 1; + } + + &:first-child { + border-radius: $radius $radius 0 0; + } + + &:last-child { + border-radius: 0 0 $radius $radius; + } + + svg { + margin-right: $gap * 1.2; + } +} diff --git a/src/containers/main/SidebarRouter/index.tsx b/src/containers/main/SidebarRouter/index.tsx index 0bf35ca6..60285f8f 100644 --- a/src/containers/main/SidebarRouter/index.tsx +++ b/src/containers/main/SidebarRouter/index.tsx @@ -1,6 +1,7 @@ import React, { FC } from 'react'; import { Route, Switch } from 'react-router'; import { TagSidebar } from '~/containers/sidebars/TagSidebar'; +import { ProfileSidebar } from '~/containers/sidebars/ProfileSidebar'; interface IProps { prefix?: string; @@ -9,6 +10,7 @@ interface IProps { const SidebarRouter: FC = ({ prefix = '' }) => ( + ); diff --git a/src/containers/sidebars/ProfileSidebar/index.tsx b/src/containers/sidebars/ProfileSidebar/index.tsx new file mode 100644 index 00000000..7b7895a3 --- /dev/null +++ b/src/containers/sidebars/ProfileSidebar/index.tsx @@ -0,0 +1,57 @@ +import React, { FC, useCallback, useEffect } from 'react'; +import styles from './styles.module.scss'; +import { SidebarWrapper } from '~/containers/sidebars/SidebarWrapper'; +import { connect } from 'react-redux'; +import { selectAuthProfile, selectAuthUser } from '~/redux/auth/selectors'; +import pick from 'ramda/es/pick'; +import { ProfileSidebarInfo } from '~/components/profile/ProfileSidebarInfo'; +import { Filler } from '~/components/containers/Filler'; +import { useHistory, useRouteMatch } from 'react-router'; +import * as USER_ACTIONS from '~/redux/auth/actions'; +import { ProfileSidebarMenu } from '~/components/profile/ProfileSidebarMenu'; +import { useCloseOnEscape } from '~/utils/hooks'; + +const mapStateToProps = state => ({ + profile: selectAuthProfile(state), + user: pick(['id'], selectAuthUser(state)), +}); + +const mapDispatchToProps = { + authLoadProfile: USER_ACTIONS.authLoadProfile, +}; + +type Props = ReturnType & typeof mapDispatchToProps & {}; + +const ProfileSidebarUnconnected: FC = ({ + profile: { is_loading, user, tab }, + user: { id }, + authLoadProfile, +}) => { + const { + params: { username }, + url, + } = useRouteMatch<{ username: string }>(); + + useEffect(() => { + authLoadProfile(username); + }, [username]); + + const history = useHistory(); + const basePath = url.replace(new RegExp(`\/~${username}$`), ''); + const onClose = useCallback(() => history.push(basePath), [basePath]); + useCloseOnEscape(onClose); + + return ( + +
+ + + +
+
+ ); +}; + +const ProfileSidebar = connect(mapStateToProps, mapDispatchToProps)(ProfileSidebarUnconnected); + +export { ProfileSidebar }; diff --git a/src/containers/sidebars/ProfileSidebar/styles.module.scss b/src/containers/sidebars/ProfileSidebar/styles.module.scss new file mode 100644 index 00000000..079b500c --- /dev/null +++ b/src/containers/sidebars/ProfileSidebar/styles.module.scss @@ -0,0 +1,5 @@ +.wrap { + @include sidebar_content; + display: flex; + flex-direction: column; +} diff --git a/src/containers/sidebars/TagSidebar/index.tsx b/src/containers/sidebars/TagSidebar/index.tsx index edeca2d0..e5b849ff 100644 --- a/src/containers/sidebars/TagSidebar/index.tsx +++ b/src/containers/sidebars/TagSidebar/index.tsx @@ -31,6 +31,7 @@ const TagSidebarUnconnected: FC = ({ nodes, tagLoadNodes, tagSetNodes }) const history = useHistory(); const basePath = url.replace(new RegExp(`\/tag\/${tag}$`), ''); + const onClose = useCallback(() => history.push(basePath), [basePath]); useEffect(() => { tagLoadNodes(tag); @@ -45,7 +46,6 @@ const TagSidebarUnconnected: FC = ({ nodes, tagLoadNodes, tagSetNodes }) const title = useMemo(() => decodeURIComponent(tag), [tag]); const progress = nodes.count > 0 ? `${(nodes.list.length / nodes.count) * 100}%` : '0'; - const onClose = useCallback(() => history.push(basePath), [basePath]); const hasMore = nodes.count > nodes.list.length; return ( diff --git a/src/containers/sidebars/TagSidebar/styles.module.scss b/src/containers/sidebars/TagSidebar/styles.module.scss index 85231a93..baa7021f 100644 --- a/src/containers/sidebars/TagSidebar/styles.module.scss +++ b/src/containers/sidebars/TagSidebar/styles.module.scss @@ -1,12 +1,5 @@ .wrap { - height: 100%; - box-sizing: border-box; - display: flex; - flex: 0 1 400px; - max-width: 100vw; - position: relative; - - @include sidebar_content; + @include sidebar_content(400px); } .content { diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 1be33dfb..2a0623be 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -78,6 +78,7 @@ $input_shadow_error: inset $red 0 0 0 1px; $input_shadow_filled: $input_shadow; $login_dialog_padding: $gap $gap 30px $gap; +$sidebar_border: transparentize(white, 0.95); @mixin outer_shadow() { box-shadow: inset transparentize(white, 0.95) 0 1px, transparentize(black, 0.8) -1px -1px; @@ -196,7 +197,15 @@ $login_dialog_padding: $gap $gap 30px $gap; } } -@mixin sidebar_content { +@mixin sidebar_content( + $width: 400px, +) { + height: 100%; + box-sizing: border-box; + display: flex; + flex: 0 1 400px; + max-width: 100vw; + position: relative; background: transparentize($content_bg, 0.4); box-shadow: transparentize(white, 0.95) -1px 0; }