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

Merge remote-tracking branch 'origin/master'

This commit is contained in:
Fedor Katurov 2021-03-21 12:46:25 +07:00
commit 167c1a8aad
102 changed files with 1560 additions and 385 deletions

View file

@ -95,7 +95,7 @@ const EditorDialogUnconnected: FC<IProps> = ({
maxLength={256}
/>
<Button title="Сохранить" iconRight="check" />
<Button title="Сохранить" iconRight="check" color={data.is_promoted ? 'primary' : 'lab'} />
</Group>
</Padder>
);

View file

@ -11,6 +11,7 @@ import { selectAuthRegisterSocial } from '~/redux/auth/selectors';
import * as AUTH_ACTIONS from '~/redux/auth/actions';
import { useCloseOnEscape } from '~/utils/hooks';
import { LoginSocialRegisterButtons } from '~/containers/dialogs/LoginSocialRegisterButtons';
import { Toggle } from '~/components/input/Toggle';
const mapStateToProps = selectAuthRegisterSocial;
const mapDispatchToProps = {
@ -21,6 +22,12 @@ const mapDispatchToProps = {
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & IDialogProps & {};
const phrase = [
'Сушёный кабачок особенно хорош в это время года, знаете ли.',
'Бывало, стреляешь по кабачку, или он стреляет в тебя.',
'Он всегда рядом, кабачок -- первый сорт! Надежда империи.',
];
const LoginSocialRegisterDialogUnconnected: FC<Props> = ({
onRequestClose,
errors,
@ -32,6 +39,7 @@ const LoginSocialRegisterDialogUnconnected: FC<Props> = ({
}) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [isDryingPants, setIsDryingPants] = useState(false);
const onSubmit = useCallback(
(event: FormEvent) => {
@ -56,7 +64,7 @@ const LoginSocialRegisterDialogUnconnected: FC<Props> = ({
useCloseOnEscape(onRequestClose);
return (
<form onSubmit={onSubmit}>
<form onSubmit={onSubmit} autoComplete="new-password">
<BetterScrollDialog
onClose={onRequestClose}
width={300}
@ -73,6 +81,7 @@ const LoginSocialRegisterDialogUnconnected: FC<Props> = ({
value={username}
title="Юзернэйм"
error={errors.username}
autoComplete="new-password"
/>
<InputText
@ -81,12 +90,18 @@ const LoginSocialRegisterDialogUnconnected: FC<Props> = ({
title="Пароль"
type="password"
error={errors.password}
autoComplete="new-password"
/>
<label className={styles.check}>
<input type="checkbox" />
<div className={styles.check} onClick={() => setIsDryingPants(!isDryingPants)}>
<Toggle value={isDryingPants} color="primary" />
<span>Это не мои штаны сушатся на радиаторе в третьей лаборатории</span>
</label>
</div>
<div className={styles.check} onClick={() => setIsDryingPants(!isDryingPants)}>
<Toggle value={!isDryingPants} color="primary" />
<span>{phrase[Math.floor(Math.random() * phrase.length)]}</span>
</div>
</Group>
</div>
</Padder>

View file

@ -0,0 +1,21 @@
import React, { FC } from 'react';
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
import styles from './styles.module.scss';
import { LabNode } from '~/components/lab/LabNode';
import { selectLabListNodes } from '~/redux/lab/selectors';
interface IProps {}
const LabGrid: FC<IProps> = () => {
const nodes = useShallowSelect(selectLabListNodes);
return (
<div className={styles.wrap}>
{nodes.map(node => (
<LabNode node={node} key={node.id} />
))}
</div>
);
};
export { LabGrid };

View file

@ -0,0 +1,8 @@
@import "~/styles/variables.scss";
.wrap {
display: grid;
grid-auto-flow: row;
grid-auto-rows: auto;
grid-row-gap: $gap;
}

View file

@ -0,0 +1,112 @@
import React, { FC, useEffect } from 'react';
import styles from './styles.module.scss';
import { Card } from '~/components/containers/Card';
import { Sticky } from '~/components/containers/Sticky';
import { Container } from '~/containers/main/Container';
import { LabGrid } from '~/containers/lab/LabGrid';
import { useDispatch } from 'react-redux';
import { labGetList } from '~/redux/lab/actions';
import { Placeholder } from '~/components/placeholders/Placeholder';
import { Grid } from '~/components/containers/Grid';
import { Group } from '~/components/containers/Group';
import { LabHero } from '~/components/lab/LabHero';
import { LabBanner } from '~/components/lab/LabBanner';
import { LabHead } from '~/components/lab/LabHead';
import { Filler } from '~/components/containers/Filler';
interface IProps {}
const LabLayout: FC<IProps> = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(labGetList());
}, [dispatch]);
return (
<div>
<Container>
<div className={styles.wrap}>
<Group className={styles.content}>
<LabHead />
<LabGrid />
</Group>
<div className={styles.panel}>
<Sticky>
<Group>
<LabBanner />
<Card>
<Group>
<Placeholder height={36} width="100%" />
<Group horizontal>
<Filler />
<Placeholder height={32} width="120px" />
</Group>
<div />
<div />
<Placeholder height={14} width="100px" />
<div />
<div className={styles.tags}>
<Placeholder height={20} width="100px" />
<Placeholder height={20} width="64px" />
<Placeholder height={20} width="100%" />
<Placeholder height={20} width="100px" />
<Placeholder height={20} width="100px" />
<Placeholder height={20} width="64px" />
</div>
<div />
<div />
<Placeholder height={14} width="180px" />
<div />
<Group className={styles.heroes}>
<LabHero />
<div />
<LabHero />
<div />
<LabHero />
<div />
<LabHero />
<div />
<LabHero />
<div />
<LabHero />
<div />
<LabHero />
</Group>
<div />
<div />
<Group>
<Placeholder width="100%" height={100} />
<Placeholder width="120px" height={16} />
</Group>
<div />
<Group>
<Placeholder width="100%" height={100} />
<Placeholder width="120px" height={16} />
</Group>
</Group>
</Card>
</Group>
</Sticky>
</div>
</div>
</Container>
</div>
);
};
export { LabLayout };

View file

@ -0,0 +1,20 @@
@import "~/styles/variables.scss";
.wrap {
display: grid;
grid-template-columns: 3fr 1fr;
column-gap: $gap;
}
.panel {
margin-top: -7px;
}
.tags {
display: flex;
flex-wrap: wrap;
& > * {
margin: 0 $gap $gap 0;
}
}

View file

@ -8,4 +8,8 @@
@include tablet {
padding: 0;
}
@media (max-width: $content_width + $gap * 4) {
padding: 0;
}
}

View file

@ -6,10 +6,14 @@ import { BorisLayout } from '~/containers/node/BorisLayout';
import { ErrorNotFound } from '~/containers/pages/ErrorNotFound';
import { ProfilePage } from '~/containers/profile/ProfilePage';
import { Redirect, Route, Switch, useLocation } from 'react-router';
import { LabLayout } from '~/containers/lab/LabLayout';
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
import { selectAuthUser } from '~/redux/auth/selectors';
interface IProps {}
const MainRouter: FC<IProps> = () => {
const { is_user } = useShallowSelect(selectAuthUser);
const location = useLocation();
return (
@ -20,6 +24,12 @@ const MainRouter: FC<IProps> = () => {
<Route path={URLS.ERRORS.NOT_FOUND} component={ErrorNotFound} />
<Route path={URLS.PROFILE_PAGE(':username')} component={ProfilePage} />
{is_user && (
<>
<Route exact path={URLS.LAB} component={LabLayout} />
</>
)}
<Redirect to="/" />
</Switch>
);

View file

@ -1,25 +1,30 @@
import React, { FC, useEffect } from 'react';
import React, { FC, useCallback, useEffect } from 'react';
import { selectNode, selectNodeComments } from '~/redux/node/selectors';
import { selectUser } from '~/redux/auth/selectors';
import { selectAuthIsTester, selectUser } from '~/redux/auth/selectors';
import { useDispatch } from 'react-redux';
import { NodeComments } from '~/components/node/NodeComments';
import styles from './styles.module.scss';
import { Group } from '~/components/containers/Group';
import boris from '~/sprites/boris_robot.svg';
import { NodeNoComments } from '~/components/node/NodeNoComments';
import { useRandomPhrase } from '~/constants/phrases';
import { NodeCommentForm } from '~/components/node/NodeCommentForm';
import isBefore from 'date-fns/isBefore';
import { Card } from '~/components/containers/Card';
import { Footer } from '~/components/main/Footer';
import { Sticky } from '~/components/containers/Sticky';
import { BorisStats } from '~/components/boris/BorisStats';
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
import { selectBorisStats } from '~/redux/boris/selectors';
import { authSetUser } from '~/redux/auth/actions';
import { authSetState, authSetUser } from '~/redux/auth/actions';
import { nodeLoadNode } from '~/redux/node/actions';
import { borisLoadStats } from '~/redux/boris/actions';
import { Container } from '~/containers/main/Container';
import StickyBox from 'react-sticky-box/dist/esnext';
import { BorisComments } from '~/components/boris/BorisComments';
import { URLS } from '~/constants/urls';
import { Route, Switch, Link } from 'react-router-dom';
import { BorisUIDemo } from '~/components/boris/BorisUIDemo';
import { BorisSuperpowers } from '~/components/boris/BorisSuperpowers';
import { Superpower } from '~/components/boris/Superpower';
import { Tabs } from '~/components/dialogs/Tabs';
import { Tab } from '~/components/dialogs/Tab';
import { useHistory, useLocation } from 'react-router';
import { Card } from '~/components/containers/Card';
type IProps = {};
@ -30,6 +35,7 @@ const BorisLayout: FC<IProps> = () => {
const user = useShallowSelect(selectUser);
const stats = useShallowSelect(selectBorisStats);
const comments = useShallowSelect(selectNodeComments);
const is_tester = useShallowSelect(selectAuthIsTester);
useEffect(() => {
const last_comment = comments[0];
@ -55,6 +61,16 @@ const BorisLayout: FC<IProps> = () => {
dispatch(borisLoadStats());
}, [dispatch]);
const setBetaTester = useCallback(
(is_tester: boolean) => {
dispatch(authSetState({ is_tester }));
},
[dispatch]
);
const history = useHistory();
const location = useLocation();
return (
<Container>
<div className={styles.wrap}>
@ -70,26 +86,40 @@ const BorisLayout: FC<IProps> = () => {
<div className={styles.container}>
<Card className={styles.content}>
<Group className={styles.grid}>
{user.is_user && <NodeCommentForm isBefore nodeId={node.current.id} />}
<Superpower>
<Tabs>
<Tab
active={location.pathname === URLS.BORIS}
onClick={() => history.push(URLS.BORIS)}
>
Комментарии
</Tab>
{node.is_loading_comments ? (
<NodeNoComments is_loading count={7} />
) : (
<NodeComments
comments={comments}
count={node.comment_count}
user={user}
order="ASC"
<Tab
active={location.pathname === `${URLS.BORIS}/ui`}
onClick={() => history.push(`${URLS.BORIS}/ui`)}
>
UI Demo
</Tab>
</Tabs>
</Superpower>
{
<Switch>
<Route path={`${URLS.BORIS}/ui`} component={BorisUIDemo} />
<BorisComments
isLoadingComments={node.is_loading_comments}
commentCount={node.comment_count}
node={node.current}
comments={node.comments}
/>
)}
</Group>
<Footer />
</Switch>
}
</Card>
<Group className={styles.stats}>
<Sticky>
<StickyBox className={styles.sticky} offsetTop={72} offsetBottom={10}>
<Group className={styles.stats__container}>
<div className={styles.stats__about}>
<h4>Господи-боженьки, где это я?</h4>
@ -102,11 +132,15 @@ const BorisLayout: FC<IProps> = () => {
<p className="grey">//&nbsp;Такова&nbsp;жизнь.</p>
</div>
<div>
{user.is_user && <BorisSuperpowers active={is_tester} onChange={setBetaTester} />}
</div>
<div className={styles.stats__wrap}>
<BorisStats stats={stats} />
</div>
</Group>
</Sticky>
</StickyBox>
</Group>
</div>
</div>

View file

@ -7,22 +7,6 @@
flex-direction: column;
}
.content {
flex: 4;
z-index: 2;
border-radius: $radius;
padding: 0;
background: $node_bg;
box-shadow: inset transparentize(mix($wisegreen, white, 60%), 0.6) 0 1px;
@include desktop {
flex: 2.5;
}
@media(max-width: 1024px) {
flex: 2;
}
}
.grid {
padding: $gap;
@ -36,7 +20,7 @@
width: 100%;
height: 100vh;
overflow: hidden;
background: 50% 0% no-repeat url('../../../sprites/boris_bg.svg');
background: 50% 0 no-repeat url('../../../sprites/boris_bg.svg');
background-size: cover;
}
@ -167,3 +151,9 @@
}
}
}
.content {
position: relative;
z-index: 1;
flex: 3;
}

View file

@ -40,7 +40,7 @@ const NodeLayout: FC<IProps> = memo(
const { head, block } = useNodeBlocks(current, is_loading);
return (
<>
<div className={styles.wrap}>
{head}
<Container>
@ -64,7 +64,7 @@ const NodeLayout: FC<IProps> = memo(
</Container>
<SidebarRouter prefix="/post:id" />
</>
</div>
);
}
);

View file

@ -2,6 +2,7 @@
.content {
align-items: stretch !important;
@include vertical_at_tablet;
}

View file

@ -29,7 +29,9 @@ const ProfileLayoutUnconnected: FC<IProps> = ({ history, nodeSetCoverImage }) =>
useEffect(() => {
if (user && user.id && user.cover) {
nodeSetCoverImage(user.cover);
return () => nodeSetCoverImage(null);
return () => {
nodeSetCoverImage(undefined);
};
}
}, [user]);

View file

@ -2,6 +2,8 @@ import React, { FC, useCallback } from 'react';
import styles from './styles.module.scss';
import classNames from 'classnames';
import { IAuthState } from '~/redux/auth/types';
import { Tabs } from '~/components/dialogs/Tabs';
import { Tab } from '~/components/dialogs/Tab';
interface IProps {
tab: string;
@ -20,28 +22,20 @@ const ProfileTabs: FC<IProps> = ({ tab, is_own, setTab }) => {
return (
<div className={styles.wrap}>
<div
className={classNames(styles.tab, { [styles.active]: tab === 'profile' })}
onClick={changeTab('profile')}
>
Профиль
</div>
<div
className={classNames(styles.tab, { [styles.active]: tab === 'messages' })}
onClick={changeTab('messages')}
>
Сообщения
</div>
{is_own && (
<>
<div
className={classNames(styles.tab, { [styles.active]: tab === 'settings' })}
onClick={changeTab('settings')}
>
<Tabs>
<Tab active={tab === 'profile'} onClick={changeTab('profile')}>
Профиль
</Tab>
<Tab active={tab === 'messages'} onClick={changeTab('messages')}>
Сообщения
</Tab>
{is_own && (
<Tab active={tab === 'settings'} onClick={changeTab('settings')}>
Настройки
</div>
</>
)}
</Tab>
)}
</Tabs>
</div>
);
};

View file

@ -1,24 +1,6 @@
@import "src/styles/variables";
.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%);
}
}