mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 21:06:42 +07:00
removed redux completely
This commit is contained in:
parent
26e6d8d41b
commit
a4bb07e9cf
323 changed files with 2464 additions and 3348 deletions
|
@ -1,5 +1,5 @@
|
|||
import React, { FC, memo } from "react";
|
||||
import styles from "./styles.module.scss";
|
||||
import React, { FC, memo } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps {}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as React from "react";
|
||||
import * as React from 'react';
|
||||
|
||||
interface IGodRaysProps {
|
||||
raised?: boolean;
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { push as historyPush } from 'connected-react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Logo } from '~/components/main/Logo';
|
||||
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { selectAuthUpdates, selectUser } from '~/redux/auth/selectors';
|
||||
import { path, pick } from 'ramda';
|
||||
import { UserButton } from '../UserButton';
|
||||
import { Notifications } from '../Notifications';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
||||
import { IState } from '~/redux/store';
|
||||
import isBefore from 'date-fns/isBefore';
|
||||
import { Authorized } from '~/components/containers/Authorized';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { useFlowStore } from '~/store/flow/useFlowStore';
|
||||
import { observer } from 'mobx-react';
|
||||
import { useShowModal } from '~/hooks/modal/useShowModal';
|
||||
import { Dialog } from '~/constants/modal';
|
||||
import { useGetLabStats } from '~/hooks/lab/useGetLabStats';
|
||||
|
||||
const mapStateToProps = (state: IState) => ({
|
||||
user: pick(['username', 'is_user', 'photo', 'last_seen_boris'])(selectUser(state)),
|
||||
updates: pick(['boris_commented_at'])(selectAuthUpdates(state)),
|
||||
pathname: path(['router', 'location', 'pathname'], state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
push: historyPush,
|
||||
authLogout: AUTH_ACTIONS.authLogout,
|
||||
authOpenProfile: AUTH_ACTIONS.authOpenProfile,
|
||||
};
|
||||
|
||||
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||
|
||||
const HeaderUnconnected: FC<IProps> = observer(
|
||||
({
|
||||
user,
|
||||
user: { is_user, last_seen_boris },
|
||||
pathname,
|
||||
updates: { boris_commented_at },
|
||||
authLogout,
|
||||
authOpenProfile,
|
||||
}) => {
|
||||
const [is_scrolled, setIsScrolled] = useState(false);
|
||||
const { updated: flowUpdates } = useFlowStore();
|
||||
const onLogin = useShowModal(Dialog.Login);
|
||||
const labStats = useGetLabStats();
|
||||
|
||||
const onScroll = useCallback(() => {
|
||||
const active = window.scrollY > 32;
|
||||
|
||||
if (active !== is_scrolled) setIsScrolled(active);
|
||||
}, [is_scrolled, setIsScrolled]);
|
||||
|
||||
useEffect(() => {
|
||||
onScroll();
|
||||
}, [onScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', onScroll);
|
||||
return () => window.removeEventListener('scroll', onScroll);
|
||||
}, [onScroll]);
|
||||
|
||||
const hasBorisUpdates = useMemo(
|
||||
() =>
|
||||
is_user &&
|
||||
boris_commented_at &&
|
||||
(!last_seen_boris || isBefore(new Date(last_seen_boris), new Date(boris_commented_at))),
|
||||
[boris_commented_at, is_user, last_seen_boris]
|
||||
);
|
||||
|
||||
const hasLabUpdates = useMemo(() => labStats.updates.length > 0, [labStats.updates]);
|
||||
const hasFlowUpdates = useMemo(() => flowUpdates.length > 0, [flowUpdates]);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.wrap, { [styles.is_scrolled]: is_scrolled })}>
|
||||
<div className={styles.container}>
|
||||
<div className={classNames(styles.logo_wrapper, { [styles.logged_in]: is_user })}>
|
||||
<Logo />
|
||||
</div>
|
||||
|
||||
<Filler className={styles.filler} />
|
||||
|
||||
<div className={styles.plugs}>
|
||||
<Authorized>
|
||||
<Link
|
||||
className={classNames(styles.item, {
|
||||
[styles.is_active]: pathname === URLS.BASE,
|
||||
[styles.has_dot]: hasFlowUpdates,
|
||||
})}
|
||||
to={URLS.BASE}
|
||||
>
|
||||
ФЛОУ
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
className={classNames(styles.item, styles.lab, {
|
||||
[styles.is_active]: pathname === URLS.LAB,
|
||||
[styles.has_dot]: hasLabUpdates,
|
||||
})}
|
||||
to={URLS.LAB}
|
||||
>
|
||||
ЛАБ
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
className={classNames(styles.item, styles.boris, {
|
||||
[styles.is_active]: pathname === URLS.BORIS,
|
||||
[styles.has_dot]: hasBorisUpdates,
|
||||
})}
|
||||
to={URLS.BORIS}
|
||||
>
|
||||
БОРИС
|
||||
</Link>
|
||||
</Authorized>
|
||||
|
||||
{is_user && (
|
||||
<div className={classNames(styles.item, styles.notifications)}>
|
||||
<Notifications />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{is_user && (
|
||||
<UserButton user={user} onLogout={authLogout} authOpenProfile={authOpenProfile} />
|
||||
)}
|
||||
|
||||
{!is_user && (
|
||||
<Button
|
||||
className={styles.user_button}
|
||||
onClick={() => onLogin({})}
|
||||
round
|
||||
color="secondary"
|
||||
>
|
||||
ВДОХ
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const Header = connect(mapStateToProps, mapDispatchToProps)(HeaderUnconnected);
|
||||
|
||||
export { Header };
|
|
@ -1,152 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
height: $header_height;
|
||||
z-index: 25;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
transition: background-color 0.5s;
|
||||
|
||||
@include desktop {
|
||||
height: 64px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.is_scrolled {
|
||||
@include blur();
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
@include container;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
font-weight: 500;
|
||||
box-sizing: border-box;
|
||||
|
||||
@include tablet {
|
||||
padding: 0 $gap;
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.plugs {
|
||||
display: flex;
|
||||
user-select: none;
|
||||
text-transform: uppercase;
|
||||
align-items: center;
|
||||
|
||||
@include tablet {
|
||||
flex: 1;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.profile {
|
||||
padding: 5px 10px;
|
||||
box-shadow: white 0 0 0 1px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.user_button {
|
||||
flex: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item {
|
||||
font: $font_16_medium;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: $gap $gap * 2;
|
||||
cursor: pointer;
|
||||
transition: color 0.25s;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: $red;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 3px;
|
||||
width: 50%;
|
||||
right: 50%;
|
||||
background: white;
|
||||
transform: translate(50%, 0) scaleX(0);
|
||||
opacity: 0;
|
||||
border-radius: 3px;
|
||||
transition: transform 0.5s, opacity 0.25s;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 4px;
|
||||
background: lighten($red, 10%);
|
||||
right: 12px;
|
||||
top: 6px;
|
||||
transition: opacity 0.5s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.has_dot {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.lab::after {
|
||||
background: lighten($blue, 10%);
|
||||
}
|
||||
|
||||
&.boris::after {
|
||||
background: lighten($wisegreen, 10%);
|
||||
}
|
||||
|
||||
@include tablet {
|
||||
padding: 0 $gap * 2 0 0;
|
||||
|
||||
&.notifications {
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-right: $gap;
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filler {
|
||||
@include tablet {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.logo_wrapper {
|
||||
|
||||
@include tablet {
|
||||
&.logged_in {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import styles from './styles.module.scss';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectAuthUpdates, selectAuthUser } from '~/redux/auth/selectors';
|
||||
import { pick } from 'ramda';
|
||||
import classNames from 'classnames';
|
||||
import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
||||
import { NotificationBubble } from '../../notifications/NotificationBubble';
|
||||
import { IMessageNotification, INotification } from '~/redux/types';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
user: pick(['last_seen_messages'], selectAuthUser(state)),
|
||||
updates: selectAuthUpdates(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
authSetLastSeenMessages: AUTH_ACTIONS.authSetLastSeenMessages,
|
||||
authOpenProfile: AUTH_ACTIONS.authOpenProfile,
|
||||
};
|
||||
|
||||
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||
|
||||
const NotificationsUnconnected: FC<IProps> = ({
|
||||
updates: { last, notifications },
|
||||
user: { last_seen_messages },
|
||||
authSetLastSeenMessages,
|
||||
authOpenProfile,
|
||||
}) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const has_new = useMemo(
|
||||
() =>
|
||||
notifications.length &&
|
||||
last &&
|
||||
Date.parse(last) &&
|
||||
(!last_seen_messages ||
|
||||
(Date.parse(last_seen_messages) && Date.parse(last) > Date.parse(last_seen_messages))),
|
||||
[last, last_seen_messages, notifications]
|
||||
);
|
||||
|
||||
const onNotificationClick = useCallback(
|
||||
(notification: INotification) => {
|
||||
switch (notification.type) {
|
||||
case 'message':
|
||||
if (!(notification as IMessageNotification)?.content?.from?.username) {
|
||||
return;
|
||||
}
|
||||
|
||||
return authOpenProfile((notification as IMessageNotification).content.from!.username);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
[authOpenProfile]
|
||||
);
|
||||
const showList = useCallback(() => setVisible(true), [setVisible]);
|
||||
const hideList = useCallback(() => setVisible(false), [setVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible || !has_new || !last) return;
|
||||
authSetLastSeenMessages(last);
|
||||
}, [visible, last, has_new, authSetLastSeenMessages]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.wrap, {
|
||||
[styles.is_new]: has_new,
|
||||
[styles.active]: notifications.length > 0,
|
||||
})}
|
||||
>
|
||||
<div className={styles.icon} onFocus={showList} onBlur={hideList} tabIndex={-1}>
|
||||
{has_new ? <Icon icon="bell_ring" size={24} /> : <Icon icon="bell" size={24} />}
|
||||
</div>
|
||||
|
||||
{visible && (
|
||||
<NotificationBubble notifications={notifications} onClick={onNotificationClick} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Notifications = connect(mapStateToProps, mapDispatchToProps)(NotificationsUnconnected);
|
||||
|
||||
export { Notifications };
|
|
@ -1,51 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
@keyframes ring {
|
||||
0% {
|
||||
transform: rotate(-10deg);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: rotate(-10deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
}
|
||||
|
||||
.wrap {
|
||||
fill: white;
|
||||
position: relative;
|
||||
outline: none;
|
||||
color: white;
|
||||
word-wrap: initial;
|
||||
white-space: initial;
|
||||
|
||||
&.active {
|
||||
.icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.is_new {
|
||||
.icon {
|
||||
animation: ring 1s infinite alternate;
|
||||
opacity: 1;
|
||||
|
||||
svg {
|
||||
fill: $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import React, { FC, useCallback, useEffect, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
import styles from "./styles.module.scss";
|
||||
import { Group } from "~/components/containers/Group";
|
||||
import React, { FC, useCallback, useEffect, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './styles.module.scss';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
|
||||
interface IProps {}
|
||||
|
||||
|
|
|
@ -1,28 +1,25 @@
|
|||
import React, { FC, useCallback } from "react";
|
||||
import { Group } from "~/components/containers/Group";
|
||||
import styles from "./styles.module.scss";
|
||||
import { getURL } from "~/utils/dom";
|
||||
import { Icon } from "~/components/input/Icon";
|
||||
import { IUser } from "~/redux/auth/types";
|
||||
import { PRESETS } from "~/constants/urls";
|
||||
import { authOpenProfile } from "~/redux/auth/actions";
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import styles from './styles.module.scss';
|
||||
import { getURL } from '~/utils/dom';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { IUser } from '~/types/auth';
|
||||
import { PRESETS } from '~/constants/urls';
|
||||
|
||||
interface IProps {
|
||||
user: Partial<IUser>;
|
||||
onLogout: () => void;
|
||||
authOpenProfile: typeof authOpenProfile;
|
||||
authOpenProfile: () => void;
|
||||
}
|
||||
|
||||
const UserButton: FC<IProps> = ({ user: { username, photo }, authOpenProfile, onLogout }) => {
|
||||
const onProfileOpen = useCallback(() => {
|
||||
if (!username) return;
|
||||
authOpenProfile(username);
|
||||
}, [authOpenProfile, username]);
|
||||
authOpenProfile();
|
||||
}, [authOpenProfile]);
|
||||
|
||||
const onSettingsOpen = useCallback(() => {
|
||||
if (!username) return;
|
||||
authOpenProfile(username);
|
||||
}, [authOpenProfile, username]);
|
||||
authOpenProfile();
|
||||
}, [authOpenProfile]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue