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

made sample profile page

This commit is contained in:
Fedor Katurov 2021-10-01 12:01:11 +07:00
parent b43fb35b47
commit a808045a7d
17 changed files with 118 additions and 179 deletions

View file

@ -3,7 +3,7 @@ import { useCommentFormFormik } from '~/utils/hooks/useCommentFormFormik';
import { FormikProvider } from 'formik'; import { FormikProvider } from 'formik';
import { LocalCommentFormTextarea } from '~/components/comment/LocalCommentFormTextarea'; import { LocalCommentFormTextarea } from '~/components/comment/LocalCommentFormTextarea';
import { Button } from '~/components/input/Button'; import { Button } from '~/components/input/Button';
import { FileUploaderProvider, useFileUploader } from '~/utils/hooks/fileUploader'; import { FileUploaderProvider, useFileUploader } from '~/utils/hooks/useFileUploader';
import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants'; import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants';
import { CommentFormAttachButtons } from '~/components/comment/CommentFormAttachButtons'; import { CommentFormAttachButtons } from '~/components/comment/CommentFormAttachButtons';
import { CommentFormFormatButtons } from '~/components/comment/CommentFormFormatButtons'; import { CommentFormFormatButtons } from '~/components/comment/CommentFormFormatButtons';

View file

@ -7,7 +7,7 @@ import { SortEnd } from 'react-sortable-hoc';
import { moveArrItem } from '~/utils/fn'; import { moveArrItem } from '~/utils/fn';
import { useDropZone } from '~/utils/hooks'; import { useDropZone } from '~/utils/hooks';
import { COMMENT_FILE_TYPES, UPLOAD_TYPES } from '~/redux/uploads/constants'; import { COMMENT_FILE_TYPES, UPLOAD_TYPES } from '~/redux/uploads/constants';
import { useFileUploaderContext } from '~/utils/hooks/fileUploader'; import { useFileUploaderContext } from '~/utils/hooks/useFileUploader';
const CommentFormAttaches: FC = () => { const CommentFormAttaches: FC = () => {
const uploader = useFileUploaderContext(); const uploader = useFileUploaderContext();

View file

@ -10,11 +10,20 @@ interface Props extends DivProps {
url?: string; url?: string;
username?: string; username?: string;
size?: number; size?: number;
preset?: typeof PRESETS[keyof typeof PRESETS];
innerRef?: React.Ref<any>; innerRef?: React.Ref<any>;
} }
const Avatar: FC<Props> = ({ url, username, size, className, innerRef, ...rest }) => { const Avatar: FC<Props> = ({
const backgroundImage = !!url ? `url('${getURLFromString(url, PRESETS.avatar)}')` : undefined; url,
username,
size,
className,
innerRef,
preset = PRESETS.avatar,
...rest
}) => {
const backgroundImage = !!url ? `url('${getURLFromString(url, preset)}')` : undefined;
const onOpenProfile = useCallback(() => openUserProfile(username), [username]); const onOpenProfile = useCallback(() => openUserProfile(username), [username]);
return ( return (

View file

@ -11,7 +11,7 @@ import { NodeEditorProps } from '~/redux/node/types';
import { useNodeImages } from '~/utils/hooks/node/useNodeImages'; import { useNodeImages } from '~/utils/hooks/node/useNodeImages';
import { useNodeAudios } from '~/utils/hooks/node/useNodeAudios'; import { useNodeAudios } from '~/utils/hooks/node/useNodeAudios';
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik'; import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
import { useFileUploaderContext } from '~/utils/hooks/fileUploader'; import { useFileUploaderContext } from '~/utils/hooks/useFileUploader';
type IProps = NodeEditorProps; type IProps = NodeEditorProps;

View file

@ -3,7 +3,7 @@ import styles from './styles.module.scss';
import { Icon } from '~/components/input/Icon'; import { Icon } from '~/components/input/Icon';
import { UPLOAD_TYPES } from '~/redux/uploads/constants'; import { UPLOAD_TYPES } from '~/redux/uploads/constants';
import { IEditorComponentProps } from '~/redux/node/types'; import { IEditorComponentProps } from '~/redux/node/types';
import { useFileUploaderContext } from '~/utils/hooks/fileUploader'; import { useFileUploaderContext } from '~/utils/hooks/useFileUploader';
import { getFileType } from '~/utils/uploader'; import { getFileType } from '~/utils/uploader';
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik'; import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
import { Button } from '~/components/input/Button'; import { Button } from '~/components/input/Button';

View file

@ -11,7 +11,7 @@ import { getURL } from '~/utils/dom';
import { Icon } from '~/components/input/Icon'; import { Icon } from '~/components/input/Icon';
import { PRESETS } from '~/constants/urls'; import { PRESETS } from '~/constants/urls';
import { IEditorComponentProps } from '~/redux/node/types'; import { IEditorComponentProps } from '~/redux/node/types';
import { useFileUploader, useFileUploaderContext } from '~/utils/hooks/fileUploader'; import { useFileUploader, useFileUploaderContext } from '~/utils/hooks/useFileUploader';
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik'; import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
import { getFileType } from '~/utils/uploader'; import { getFileType } from '~/utils/uploader';

View file

@ -6,7 +6,7 @@ import { selectUploads } from '~/redux/uploads/selectors';
import { ImageGrid } from '~/components/editors/ImageGrid'; import { ImageGrid } from '~/components/editors/ImageGrid';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import { NodeEditorProps } from '~/redux/node/types'; import { NodeEditorProps } from '~/redux/node/types';
import { useFileUploaderContext } from '~/utils/hooks/fileUploader'; import { useFileUploaderContext } from '~/utils/hooks/useFileUploader';
type IProps = NodeEditorProps; type IProps = NodeEditorProps;

View file

@ -7,7 +7,7 @@ import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
import { prop } from 'ramda'; import { prop } from 'ramda';
import { useNodeFormFormik } from '~/utils/hooks/useNodeFormFormik'; import { useNodeFormFormik } from '~/utils/hooks/useNodeFormFormik';
import { EditorButtons } from '~/components/editors/EditorButtons'; import { EditorButtons } from '~/components/editors/EditorButtons';
import { FileUploaderProvider, useFileUploader } from '~/utils/hooks/fileUploader'; import { FileUploaderProvider, useFileUploader } from '~/utils/hooks/useFileUploader';
import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants'; import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants';
import { FormikProvider } from 'formik'; import { FormikProvider } from 'formik';
import { INode } from '~/redux/types'; import { INode } from '~/redux/types';

View file

@ -9,6 +9,9 @@ import classNames from 'classnames';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import markdown from '~/styles/common/markdown.module.scss'; import markdown from '~/styles/common/markdown.module.scss';
import { ProfileAvatar } from '~/containers/profile/ProfileAvatar';
import { Avatar } from '~/components/common/Avatar';
import { Markdown } from '~/components/containers/Markdown';
interface IProps { interface IProps {
profile: IAuthState['profile']; profile: IAuthState['profile'];
@ -16,17 +19,15 @@ interface IProps {
} }
const ProfilePageLeft: FC<IProps> = ({ username, profile }) => { 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 ( return (
<div className={styles.wrap}> <div className={styles.wrap}>
<div className={styles.avatar} style={{ backgroundImage: `url('${thumb}')` }} /> <Avatar
username={username}
url={profile.user?.photo?.url}
className={styles.avatar}
preset={PRESETS['600']}
/>
<div className={styles.region_wrap}>
<div className={styles.region}> <div className={styles.region}>
<div className={styles.name}> <div className={styles.name}>
{profile.is_loading ? <Placeholder /> : profile?.user?.fullname} {profile.is_loading ? <Placeholder /> : profile?.user?.fullname}
@ -35,30 +36,13 @@ const ProfilePageLeft: FC<IProps> = ({ username, profile }) => {
<div className={styles.username}> <div className={styles.username}>
{profile.is_loading ? <Placeholder /> : `~${profile?.user?.username}`} {profile.is_loading ? <Placeholder /> : `~${profile?.user?.username}`}
</div> </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>
{profile && profile.user && profile.user.description && false && ( {profile && profile.user && profile.user.description && (
<div className={classNames(styles.description, markdown.wrapper)}> <Markdown
{formatText(profile?.user?.description || '')} className={styles.description}
</div> dangerouslySetInnerHTML={{ __html: formatText(profile.user.description) }}
/>
)} )}
</div> </div>
); );

View file

@ -1,92 +1,51 @@
@import "src/styles/variables"; @import "src/styles/variables";
.wrap { .wrap {
@include outer_shadow;
padding: $gap $gap $gap * 2;
box-sizing: border-box;
display: flex; display: flex;
align-items: stretch; align-items: stretch;
justify-content: stretch; justify-content: stretch;
flex-direction: column; flex-direction: column;
background: $comment_bg;
height: 100%;
border-radius: $radius;
} }
.avatar { .avatar {
width: 100%; width: 100%;
padding-bottom: 75%; height: 0;
border-radius: 0 $radius 0 0; padding-bottom: 100%;
background: 50% 50% no-repeat; margin-bottom: $gap * 2;
background-size: cover;
}
.region_wrap {
width: 100%;
// padding: 0 10px;
position: relative;
margin-top: -$radius;
box-sizing: border-box;
} }
.region { .region {
// background: $content_bg;
background: darken($content_bg, 2%);
width: 100%; width: 100%;
border-radius: 0 $radius $radius 0; text-align: center;
} }
.name { .name {
font: $font_24_semibold; font: $font_24_semibold;
color: white; color: white;
padding: $gap $gap 0 $gap;
text-transform: uppercase; text-transform: uppercase;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
margin-bottom: 4px;
} }
.username { .username {
font: $font_14_semibold; font: $font_14_regular;
padding: 0 $gap $gap $gap;
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
color: transparentize(white, 0.5); color: transparentize(white, 0.5);
margin-top: $gap / 2;
}
.menu {
padding: $gap 0 $gap 0;
display: flex;
align-items: stretch;
width: 100%;
flex-direction: column;
box-sizing: border-box;
display: none;
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;
}
} }
.description { .description {
padding: $gap; @include clamp(3, 21px * 3);
box-sizing: border-box; line-height: 21px;
// background: darken($content_bg, 2%); font: $font_14_regular;
background: darken($content_bg, 4%); margin-top: $gap * 3;
// margin: 0 $gap; display: none;
border-radius: 0 0 $radius $radius;
} }

View file

@ -9,66 +9,15 @@
$cols: $content_width / $cell; $cols: $content_width / $cell;
@mixin fluid {
@media(min-width: $content_width) {
.fluid & {
@content
}
}
}
.container { .container {
max-width: $content_width; max-width: $content_width;
width: 100%; width: 100%;
&.fluid {
padding: 0 $gap;
box-sizing: border-box;
max-width: none;
}
} }
.grid { .grid {
width: 100%;
box-sizing: border-box;
display: grid;
grid-template-columns: repeat(auto-fit, minmax($cell - 5, 1fr));
grid-template-rows: 50vh $cell; grid-template-rows: 50vh $cell;
grid-auto-rows: $cell;
grid-auto-flow: row dense; @include flow_grid;
grid-column-gap: $gap;
grid-row-gap: $gap;
@include fluid {
grid-template-columns: repeat(auto-fit, minmax($fluid_cell - 5, 1fr));
grid-template-rows: $fluid_cell;
grid-auto-rows: $fluid_cell;
}
@media (max-width: ($cell + 10) * 3) {
grid-template-columns: repeat(auto-fill, minmax($fluid_cell - 20, 1fr));
grid-auto-rows: $fluid_cell;
grid-template-rows: calc(50vw - 10px) $fluid_cell;
}
@media (max-width: $cell_tablet) {
grid-template-rows: calc(66vw - 10px) auto $fluid_cell;
}
@media (max-width: $cell_mobile) {
// rework stamp, so it will be shown as smaller one on mobiles
grid-template-columns: repeat(auto-fill, minmax(calc(50vw - 20px), 1fr));
grid-template-rows: calc(80vw - 10px) auto 50vw;
grid-auto-rows: 50vw;
}
@media (max-width: ($fluid_cell + 5) * 1.5 + 20) {
grid-template-columns: repeat(auto-fill, minmax(calc(50vw - 20px), 1fr));
grid-template-rows: calc(80vw - 10px) auto 50vw;
grid-auto-rows: 50vw;
}
} }
.pad_last { .pad_last {
@ -86,15 +35,6 @@ $cols: $content_width / $cell;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font: $font_24_semibold; font: $font_24_semibold;
@include fluid {
grid-row-end: span 2;
grid-column-end: span 4;
@media(max-width: $content_width) {
grid-column-end: -1;
}
}
} }
.stamp { .stamp {

View file

@ -4,9 +4,13 @@ import { Route, RouteComponentProps, Switch } from 'react-router';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { authLoadProfile } from '~/redux/auth/actions'; import { authLoadProfile } from '~/redux/auth/actions';
import { useShallowSelect } from '~/utils/hooks/useShallowSelect'; import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
import { selectAuthProfile } from '~/redux/auth/selectors'; import { selectAuthProfile, selectUser } from '~/redux/auth/selectors';
import { ProfilePageLeft } from '~/containers/profile/ProfilePageLeft'; import { ProfilePageLeft } from '~/containers/profile/ProfilePageLeft';
import { Container } from '~/containers/main/Container'; import { Container } from '~/containers/main/Container';
import { FlowGrid } from '~/components/flow/FlowGrid';
import { FlowLayout } from '~/layouts/FlowLayout';
import { Sticky } from '~/components/containers/Sticky';
import { selectFlow } from '~/redux/flow/selectors';
type Props = RouteComponentProps<{ username: string }> & {}; type Props = RouteComponentProps<{ username: string }> & {};
@ -15,6 +19,9 @@ const ProfileLayout: FC<Props> = ({
params: { username }, params: { username },
}, },
}) => { }) => {
const { nodes } = useShallowSelect(selectFlow);
const user = useShallowSelect(selectUser);
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
@ -25,7 +32,15 @@ const ProfileLayout: FC<Props> = ({
return ( return (
<Container className={styles.wrap}> <Container className={styles.wrap}>
<div className={styles.left}>
<Sticky>
<ProfilePageLeft profile={profile} username={username} /> <ProfilePageLeft profile={profile} username={username} />
</Sticky>
</div>
<div className={styles.grid}>
<FlowGrid nodes={nodes} user={user} onChangeCellView={console.log} />
</div>
</Container> </Container>
); );
}; };

View file

@ -1,20 +1,16 @@
@import "src/styles/variables"; @import "src/styles/variables";
.wrap { .wrap {
flex: 1; display: grid;
display: flex; grid-template-columns: $cell auto;
align-items: stretch; grid-column-gap: $gap;
justify-content: stretch;
border-radius: $radius;
} }
.grid {
@include flow_grid;
}
.left { .left {
flex: 1;
background: darken($content_bg, 2%);
border-radius: 0 $radius $radius 0;
box-sizing: border-box;
} }
.right { .right {
flex: 4;
} }

View file

@ -268,3 +268,39 @@ $sidebar_border: transparentize(white, 0.95);
$color: mix($wisegreen, $content_bg, 30%); $color: mix($wisegreen, $content_bg, 30%);
background: linear-gradient(170deg, lighten($color, 10%), $color); background: linear-gradient(170deg, lighten($color, 10%), $color);
} }
@mixin flow_grid {
width: 100%;
box-sizing: border-box;
display: grid;
grid-template-columns: repeat(auto-fit, minmax($cell - 5, 1fr));
grid-auto-rows: $cell;
grid-auto-flow: row dense;
grid-column-gap: $gap;
grid-row-gap: $gap;
@media (max-width: ($cell + 10) * 3) {
grid-template-columns: repeat(auto-fill, minmax($fluid_cell - 20, 1fr));
grid-auto-rows: $fluid_cell;
grid-template-rows: calc(50vw - 10px) $fluid_cell;
}
@media (max-width: $cell_tablet) {
grid-template-rows: calc(66vw - 10px) auto $fluid_cell;
}
@media (max-width: $cell_mobile) {
// rework stamp, so it will be shown as smaller one on mobiles
grid-template-columns: repeat(auto-fill, minmax(calc(50vw - 20px), 1fr));
grid-template-rows: calc(80vw - 10px) auto 50vw;
grid-auto-rows: 50vw;
}
@media (max-width: ($fluid_cell + 5) * 1.5 + 20) {
grid-template-columns: repeat(auto-fill, minmax(calc(50vw - 20px), 1fr));
grid-template-rows: calc(80vw - 10px) auto 50vw;
grid-auto-rows: 50vw;
}
}

View file

@ -2,7 +2,7 @@ import { IComment, INode } from '~/redux/types';
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react';
import { FormikHelpers, useFormik, useFormikContext } from 'formik'; import { FormikHelpers, useFormik, useFormikContext } from 'formik';
import { array, object, string } from 'yup'; import { array, object, string } from 'yup';
import { FileUploader } from '~/utils/hooks/fileUploader'; import { FileUploader } from '~/utils/hooks/useFileUploader';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { nodePostLocalComment } from '~/redux/node/actions'; import { nodePostLocalComment } from '~/redux/node/actions';

View file

@ -1,5 +1,5 @@
import { IComment, INode } from '~/redux/types'; import { IComment, INode } from '~/redux/types';
import { FileUploader } from '~/utils/hooks/fileUploader'; import { FileUploader } from '~/utils/hooks/useFileUploader';
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react';
import { FormikHelpers, useFormik, useFormikContext } from 'formik'; import { FormikHelpers, useFormik, useFormikContext } from 'formik';
import { object, string } from 'yup'; import { object, string } from 'yup';