mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
#23 added superpowers switch
This commit is contained in:
parent
e38090c755
commit
756840f173
16 changed files with 178 additions and 25 deletions
27
src/components/boris/BorisSuperpowers/index.tsx
Normal file
27
src/components/boris/BorisSuperpowers/index.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React, { FC } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { Toggle } from '~/components/input/Toggle';
|
||||
|
||||
interface IProps {
|
||||
active?: boolean;
|
||||
onChange?: (val: boolean) => void;
|
||||
}
|
||||
|
||||
const BorisSuperpowers: FC<IProps> = ({ active, onChange }) => (
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.toggle}>
|
||||
<Toggle value={active} handler={onChange} />
|
||||
</div>
|
||||
|
||||
<div className={styles.left}>
|
||||
<div className={styles.title}>Суперспособности</div>
|
||||
{active ? (
|
||||
<div className={styles.subtitle}>Включи, чтобы видеть будущее</div>
|
||||
) : (
|
||||
<div className={styles.subtitle}>Ты видишь всё, что скрыто</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export { BorisSuperpowers };
|
19
src/components/boris/BorisSuperpowers/styles.module.scss
Normal file
19
src/components/boris/BorisSuperpowers/styles.module.scss
Normal file
|
@ -0,0 +1,19 @@
|
|||
@import "~/styles/variables";
|
||||
|
||||
.wrap {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
column-gap: $gap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font: $font_14_semibold;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font: $font_12_regular;
|
||||
color: transparentize(white, 0.5);
|
||||
}
|
16
src/components/boris/Superpower/index.tsx
Normal file
16
src/components/boris/Superpower/index.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
import React, { FC, memo } from 'react';
|
||||
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
||||
import { selectAuthIsTester, selectUser } from '~/redux/auth/selectors';
|
||||
|
||||
interface IProps {}
|
||||
|
||||
const Superpower: FC<IProps> = memo(({ children }) => {
|
||||
const user = useShallowSelect(selectUser);
|
||||
const is_tester = useShallowSelect(selectAuthIsTester);
|
||||
|
||||
if (!user || !is_tester) return null;
|
||||
|
||||
return <>{children}</>;
|
||||
});
|
||||
|
||||
export { Superpower };
|
|
@ -2,6 +2,7 @@ import React, { FC, useCallback, useEffect, useRef } from 'react';
|
|||
import { IEditorComponentProps } from '~/redux/node/types';
|
||||
import { usePopper } from 'react-popper';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Superpower } from '~/components/boris/Superpower';
|
||||
|
||||
interface IProps extends IEditorComponentProps {}
|
||||
|
||||
|
@ -11,24 +12,22 @@ const EditorPublicSwitch: FC<IProps> = ({ data, setData }) => {
|
|||
setData,
|
||||
]);
|
||||
|
||||
if (process.env.REACT_APP_LAB_ENABLED !== '1') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
color={data.is_promoted ? 'primary' : 'secondary'}
|
||||
type="button"
|
||||
iconLeft={data.is_promoted ? 'waves' : 'lab'}
|
||||
size="giant"
|
||||
label={
|
||||
data.is_promoted
|
||||
? 'Доступно всем на главной странице'
|
||||
: 'Видно только сотрудникам в лаборатории'
|
||||
}
|
||||
onClick={onChange}
|
||||
round
|
||||
/>
|
||||
<Superpower>
|
||||
<Button
|
||||
color={data.is_promoted ? 'primary' : 'secondary'}
|
||||
type="button"
|
||||
iconLeft={data.is_promoted ? 'waves' : 'lab'}
|
||||
size="giant"
|
||||
label={
|
||||
data.is_promoted
|
||||
? 'Доступно всем на главной странице'
|
||||
: 'Видно только сотрудникам в лаборатории'
|
||||
}
|
||||
onClick={onChange}
|
||||
round
|
||||
/>
|
||||
</Superpower>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
28
src/components/input/Toggle/index.tsx
Normal file
28
src/components/input/Toggle/index.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface IProps {
|
||||
value?: boolean;
|
||||
handler?: (val: boolean) => void;
|
||||
}
|
||||
|
||||
const Toggle: FC<IProps> = ({ value, handler }) => {
|
||||
const onClick = useCallback(() => {
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler(!value);
|
||||
}, [value, handler]);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(styles.toggle, { [styles.active]: value })}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Toggle };
|
35
src/components/input/Toggle/styles.module.scss
Normal file
35
src/components/input/Toggle/styles.module.scss
Normal file
|
@ -0,0 +1,35 @@
|
|||
@import "~/styles/variables.scss";
|
||||
|
||||
.toggle {
|
||||
height: 24px;
|
||||
width: 48px;
|
||||
border-radius: 12px;
|
||||
background-color: transparentize(white, 0.9);
|
||||
display: flex;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: 11px;
|
||||
background-color: darken(white, 50%);
|
||||
transform: translate(0, 0);
|
||||
transition: transform 0.25s, color 0.25s, background-color;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $wisegreen;
|
||||
|
||||
&::after {
|
||||
transform: translate(24px, 0);
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import * as MODAL_ACTIONS from '~/redux/modal/actions';
|
|||
import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
||||
import { IState } from '~/redux/store';
|
||||
import isBefore from 'date-fns/isBefore';
|
||||
import { Superpower } from '~/components/boris/Superpower';
|
||||
|
||||
const mapStateToProps = (state: IState) => ({
|
||||
user: pick(['username', 'is_user', 'photo', 'last_seen_boris'])(selectUser(state)),
|
||||
|
@ -89,14 +90,14 @@ const HeaderUnconnected: FC<IProps> = memo(
|
|||
ФЛОУ
|
||||
</Link>
|
||||
|
||||
{is_user && process.env.REACT_APP_LAB_ENABLED === '1' && (
|
||||
<Superpower>
|
||||
<Link
|
||||
className={classNames(styles.item, { [styles.is_active]: pathname === URLS.BASE })}
|
||||
to={URLS.LAB}
|
||||
>
|
||||
ЛАБ
|
||||
</Link>
|
||||
)}
|
||||
</Superpower>
|
||||
|
||||
<Link
|
||||
className={classNames(styles.item, {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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';
|
||||
|
@ -15,11 +15,12 @@ import { Footer } from '~/components/main/Footer';
|
|||
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 { BorisSuperpowers } from '~/components/boris/BorisSuperpowers';
|
||||
|
||||
type IProps = {};
|
||||
|
||||
|
@ -30,6 +31,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 +57,13 @@ const BorisLayout: FC<IProps> = () => {
|
|||
dispatch(borisLoadStats());
|
||||
}, [dispatch]);
|
||||
|
||||
const setBetaTester = useCallback(
|
||||
(is_tester: boolean) => {
|
||||
dispatch(authSetState({ is_tester }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<div className={styles.wrap}>
|
||||
|
@ -102,6 +111,10 @@ const BorisLayout: FC<IProps> = () => {
|
|||
<p className="grey">// Такова жизнь.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{user.is_user && <BorisSuperpowers active={is_tester} onChange={setBetaTester} />}
|
||||
</div>
|
||||
|
||||
<div className={styles.stats__wrap}>
|
||||
<BorisStats stats={stats} />
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,11 @@ export const authSetToken = (token: IAuthState['token']) => ({
|
|||
token,
|
||||
});
|
||||
|
||||
export const authSetState = (payload: Partial<IAuthState>) => ({
|
||||
type: AUTH_USER_ACTIONS.SET_STATE,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const gotAuthPostMessage = ({ token }: { token: string }) => ({
|
||||
type: AUTH_USER_ACTIONS.GOT_AUTH_POST_MESSAGE,
|
||||
token,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { api, cleanResult, errorMiddleware, resultMiddleware } from '~/utils/api';
|
||||
import { api, cleanResult } from '~/utils/api';
|
||||
import { API } from '~/constants/api';
|
||||
import { IResultWithStatus } from '~/redux/types';
|
||||
import {
|
||||
ApiAttachSocialRequest,
|
||||
ApiAttachSocialResult,
|
||||
|
|
|
@ -3,6 +3,7 @@ import { IToken, IUser } from '~/redux/auth/types';
|
|||
export const AUTH_USER_ACTIONS = {
|
||||
SEND_LOGIN_REQUEST: 'SEND_LOGIN_REQUEST',
|
||||
SET_LOGIN_ERROR: 'SET_LOGIN_ERROR',
|
||||
SET_STATE: 'SET_STATE',
|
||||
SET_USER: 'SET_USER',
|
||||
SET_TOKEN: 'SET_TOKEN',
|
||||
|
||||
|
|
|
@ -25,6 +25,11 @@ const setUser: ActionHandler<typeof ActionCreators.authSetUser> = (state, { prof
|
|||
},
|
||||
});
|
||||
|
||||
const setState: ActionHandler<typeof ActionCreators.authSetState> = (state, { payload }) => ({
|
||||
...state,
|
||||
...payload,
|
||||
});
|
||||
|
||||
const setToken: ActionHandler<typeof ActionCreators.authSetToken> = (state, { token }) => ({
|
||||
...state,
|
||||
token,
|
||||
|
@ -104,6 +109,7 @@ const setRegisterSocialErrors: ActionHandler<typeof ActionCreators.authSetRegist
|
|||
export const AUTH_USER_HANDLERS = {
|
||||
[AUTH_USER_ACTIONS.SET_LOGIN_ERROR]: setLoginError,
|
||||
[AUTH_USER_ACTIONS.SET_USER]: setUser,
|
||||
[AUTH_USER_ACTIONS.SET_STATE]: setState,
|
||||
[AUTH_USER_ACTIONS.SET_TOKEN]: setToken,
|
||||
[AUTH_USER_ACTIONS.SET_PROFILE]: setProfile,
|
||||
[AUTH_USER_ACTIONS.SET_UPDATES]: setUpdates,
|
||||
|
|
|
@ -10,6 +10,7 @@ const HANDLERS = {
|
|||
const INITIAL_STATE: IAuthState = {
|
||||
token: '',
|
||||
user: { ...EMPTY_USER },
|
||||
is_tester: false,
|
||||
|
||||
updates: {
|
||||
last: '',
|
||||
|
|
|
@ -2,6 +2,7 @@ import { IState } from '~/redux/store';
|
|||
|
||||
export const selectAuth = (state: IState) => state.auth;
|
||||
export const selectUser = (state: IState) => state.auth.user;
|
||||
export const selectAuthIsTester = (state: IState) => state.auth.is_tester;
|
||||
export const selectToken = (state: IState) => state.auth.token;
|
||||
export const selectAuthLogin = (state: IState) => state.auth.login;
|
||||
export const selectAuthProfile = (state: IState) => state.auth.profile;
|
||||
|
|
|
@ -37,6 +37,8 @@ export type IAuthState = Readonly<{
|
|||
user: IUser;
|
||||
token: string;
|
||||
|
||||
is_tester: boolean;
|
||||
|
||||
updates: {
|
||||
last: string;
|
||||
notifications: INotification[];
|
||||
|
|
|
@ -46,7 +46,7 @@ import { assocPath } from 'ramda';
|
|||
|
||||
const authPersistConfig: PersistConfig = {
|
||||
key: 'auth',
|
||||
whitelist: ['token', 'user', 'updates'],
|
||||
whitelist: ['token', 'user', 'updates', 'is_tester'],
|
||||
storage,
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue