mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
added superpowers toggle to sidebar
This commit is contained in:
parent
32aaa1e8db
commit
d652a76640
25 changed files with 400 additions and 283 deletions
|
@ -1,10 +1,10 @@
|
||||||
import { FC, memo, useCallback, useEffect, useRef, useState } from "react";
|
import { FC, memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { debounce, throttle } from "throttle-debounce";
|
import { debounce, throttle } from 'throttle-debounce';
|
||||||
|
|
||||||
import { useWindowSize } from "~/hooks/dom/useWindowSize";
|
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
||||||
|
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
interface LoginSceneProps {}
|
interface LoginSceneProps {}
|
||||||
|
|
||||||
|
@ -17,31 +17,31 @@ interface Layer {
|
||||||
|
|
||||||
const layers: Layer[] = [
|
const layers: Layer[] = [
|
||||||
{
|
{
|
||||||
src: "/images/clouds__bg.svg",
|
src: '/images/clouds__bg.svg',
|
||||||
velocity: -0.3,
|
velocity: -0.3,
|
||||||
width: 3840,
|
width: 3840,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: "/images/clouds__cube.svg",
|
src: '/images/clouds__cube.svg',
|
||||||
velocity: -0.1,
|
velocity: -0.1,
|
||||||
width: 3840,
|
width: 3840,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: "/images/clouds__cloud.svg",
|
src: '/images/clouds__cloud.svg',
|
||||||
velocity: 0.2,
|
velocity: 0.2,
|
||||||
width: 3840,
|
width: 3840,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: "/images/clouds__dudes.svg",
|
src: '/images/clouds__dudes.svg',
|
||||||
velocity: 0.5,
|
velocity: 0.5,
|
||||||
width: 3840,
|
width: 3840,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: "/images/clouds__trash.svg",
|
src: '/images/clouds__trash.svg',
|
||||||
velocity: 0.8,
|
velocity: 0.8,
|
||||||
width: 3840,
|
width: 3840,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
|
@ -52,7 +52,7 @@ const LoginScene: FC<LoginSceneProps> = memo(() => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
const imageRefs = useRef<Array<SVGImageElement | null>>([]);
|
const imageRefs = useRef<Array<SVGImageElement | null>>([]);
|
||||||
const { isMobile } = useWindowSize();
|
const { isTablet } = useWindowSize();
|
||||||
const domRect = useRef<DOMRect>();
|
const domRect = useRef<DOMRect>();
|
||||||
|
|
||||||
const onMouseMove = useCallback(
|
const onMouseMove = useCallback(
|
||||||
|
@ -84,11 +84,11 @@ const LoginScene: FC<LoginSceneProps> = memo(() => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = throttle(100, onMouseMove);
|
const listener = throttle(100, onMouseMove);
|
||||||
document.addEventListener("mousemove", listener);
|
document.addEventListener('mousemove', listener);
|
||||||
return () => document.removeEventListener("mousemove", listener);
|
return () => document.removeEventListener('mousemove', listener);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (isMobile) {
|
if (isTablet) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,16 +103,16 @@ const LoginScene: FC<LoginSceneProps> = memo(() => {
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="fallbackGradient" x1={0} x2={0} y1={1} y2={0}>
|
<linearGradient id="fallbackGradient" x1={0} x2={0} y1={1} y2={0}>
|
||||||
<stop style={{ stopColor: "#ffccaa", stopOpacity: 1 }} offset="0" />
|
<stop style={{ stopColor: '#ffccaa', stopOpacity: 1 }} offset="0" />
|
||||||
<stop
|
<stop
|
||||||
style={{ stopColor: "#fff6d5", stopOpacity: 1 }}
|
style={{ stopColor: '#fff6d5', stopOpacity: 1 }}
|
||||||
offset="0.34655526"
|
offset="0.34655526"
|
||||||
/>
|
/>
|
||||||
<stop
|
<stop
|
||||||
style={{ stopColor: "#afc6e9", stopOpacity: 1 }}
|
style={{ stopColor: '#afc6e9', stopOpacity: 1 }}
|
||||||
offset="0.765342"
|
offset="0.765342"
|
||||||
/>
|
/>
|
||||||
<stop style={{ stopColor: "#879fde", stopOpacity: 1 }} offset="1" />
|
<stop style={{ stopColor: '#879fde', stopOpacity: 1 }} offset="1" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
import React, { FC } from 'react';
|
|
||||||
|
|
||||||
import { BorisContacts } from '~/components/boris/BorisContacts';
|
|
||||||
import { BorisStats } from '~/components/boris/BorisStats';
|
|
||||||
import { BorisSuperpowers } from '~/components/boris/BorisSuperpowers';
|
|
||||||
import { Group } from '~/components/containers/Group';
|
|
||||||
import styles from '~/layouts/BorisLayout/styles.module.scss';
|
|
||||||
import { BorisUsageStats } from '~/types/boris';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
isUser: boolean;
|
|
||||||
isTester: boolean;
|
|
||||||
stats: BorisUsageStats;
|
|
||||||
setBetaTester: (val: boolean) => void;
|
|
||||||
isLoading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BorisSidebar: FC<Props> = ({ isUser, stats, isLoading, isTester, setBetaTester }) => (
|
|
||||||
<Group className={styles.stats__container}>
|
|
||||||
<div className={styles.super_powers}>
|
|
||||||
{isUser && <BorisSuperpowers active={isTester} onChange={setBetaTester} />}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<BorisContacts />
|
|
||||||
|
|
||||||
<div className={styles.stats__wrap}>
|
|
||||||
<BorisStats stats={stats} isLoading={isLoading} />
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
|
|
||||||
export { BorisSidebar };
|
|
|
@ -1,15 +1,17 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from "react";
|
||||||
|
|
||||||
import { useAuth } from '~/hooks/auth/useAuth';
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
|
import { useAuth } from "~/hooks/auth/useAuth";
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
const Superpower: FC<IProps> = ({ children }) => {
|
const Superpower: FC<IProps> = observer(({ children }) => {
|
||||||
const { isTester } = useAuth();
|
const { isTester } = useAuth();
|
||||||
|
|
||||||
if (!isTester) return null;
|
if (!isTester) return null;
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
});
|
||||||
|
|
||||||
export { Superpower };
|
export { Superpower };
|
||||||
|
|
|
@ -16,7 +16,11 @@ const Zone: FC<ZoneProps> = ({
|
||||||
children,
|
children,
|
||||||
color = "normal",
|
color = "normal",
|
||||||
}) => (
|
}) => (
|
||||||
<div className={classNames(className, styles.pad, styles[color])}>
|
<div
|
||||||
|
className={classNames(className, styles.pad, styles[color], {
|
||||||
|
[styles.with_title]: !!title,
|
||||||
|
})}
|
||||||
|
>
|
||||||
{!!title && (
|
{!!title && (
|
||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
|
|
|
@ -8,7 +8,7 @@ $pad_usual: mix(white, $content_bg, 10%);
|
||||||
|
|
||||||
span {
|
span {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -5px;
|
top: -$gap;
|
||||||
left: $radius;
|
left: $radius;
|
||||||
transform: translate(0, -100%);
|
transform: translate(0, -100%);
|
||||||
background: $pad_usual;
|
background: $pad_usual;
|
||||||
|
@ -25,7 +25,7 @@ $pad_usual: mix(white, $content_bg, 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pad {
|
.pad {
|
||||||
padding: $gap * 1.5 $gap $gap;
|
padding: $gap;
|
||||||
box-shadow: inset $pad_usual 0 0 0 2px;
|
box-shadow: inset $pad_usual 0 0 0 2px;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -33,4 +33,8 @@ $pad_usual: mix(white, $content_bg, 10%);
|
||||||
&.danger {
|
&.danger {
|
||||||
box-shadow: inset $pad_danger 0 0 0 2px;
|
box-shadow: inset $pad_danger 0 0 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.with_title {
|
||||||
|
padding-top: $gap * 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from "react";
|
||||||
|
|
||||||
import { Filler } from '~/components/containers/Filler';
|
import { Filler } from "~/components/containers/Filler";
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from "~/components/containers/Group";
|
||||||
import { Padder } from '~/components/containers/Padder';
|
import { Padder } from "~/components/containers/Padder";
|
||||||
import { EditorActionsPanel } from '~/components/editors/EditorActionsPanel';
|
import { EditorActionsPanel } from "~/components/editors/EditorActionsPanel";
|
||||||
import { Button } from '~/components/input/Button';
|
import { Button } from "~/components/input/Button";
|
||||||
import { InputText } from '~/components/input/InputText';
|
import { InputText } from "~/components/input/InputText";
|
||||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
import { useWindowSize } from "~/hooks/dom/useWindowSize";
|
||||||
import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik';
|
import { useNodeFormContext } from "~/hooks/node/useNodeFormFormik";
|
||||||
|
|
||||||
const EditorButtons: FC = () => {
|
const EditorButtons: FC = () => {
|
||||||
const { values, handleChange, isSubmitting } = useNodeFormContext();
|
const { values, handleChange, isSubmitting } = useNodeFormContext();
|
||||||
const { isMobile } = useWindowSize();
|
const { isTablet } = useWindowSize();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Padder style={{ position: 'relative' }}>
|
<Padder style={{ position: "relative" }}>
|
||||||
<EditorActionsPanel />
|
<EditorActionsPanel />
|
||||||
|
|
||||||
<Group horizontal>
|
<Group horizontal>
|
||||||
|
@ -22,17 +22,17 @@ const EditorButtons: FC = () => {
|
||||||
<InputText
|
<InputText
|
||||||
title="Название"
|
title="Название"
|
||||||
value={values.title}
|
value={values.title}
|
||||||
handler={handleChange('title')}
|
handler={handleChange("title")}
|
||||||
autoFocus={!isMobile}
|
autoFocus={!isTablet}
|
||||||
maxLength={256}
|
maxLength={256}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
</Filler>
|
</Filler>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
title={isMobile ? undefined : 'Сохранить'}
|
title={isTablet ? undefined : "Сохранить"}
|
||||||
iconRight="check"
|
iconRight="check"
|
||||||
color={values.is_promoted ? 'primary' : 'lab'}
|
color={values.is_promoted ? "primary" : "lab"}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
type="submit"
|
type="submit"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { FC, useCallback } from 'react';
|
import React, { FC, useCallback } from "react";
|
||||||
|
|
||||||
import { SortableImageGrid } from '~/components/sortable';
|
import { SortableImageGrid } from "~/components/sortable";
|
||||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
import { useWindowSize } from "~/hooks/dom/useWindowSize";
|
||||||
import { UploadStatus } from '~/store/uploader/UploaderStore';
|
import { UploadStatus } from "~/store/uploader/UploaderStore";
|
||||||
import { IFile } from '~/types';
|
import { IFile } from "~/types";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
files: IFile[];
|
files: IFile[];
|
||||||
|
@ -12,20 +12,20 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
|
const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
|
||||||
const { isMobile } = useWindowSize();
|
const { isTablet } = useWindowSize();
|
||||||
|
|
||||||
const onMove = useCallback(
|
const onMove = useCallback(
|
||||||
(newFiles: IFile[]) => {
|
(newFiles: IFile[]) => {
|
||||||
setFiles(newFiles.filter(it => it));
|
setFiles(newFiles.filter(it => it));
|
||||||
},
|
},
|
||||||
[setFiles, files]
|
[setFiles, files],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(id: IFile['id']) => {
|
(id: IFile["id"]) => {
|
||||||
setFiles(files.filter(file => file && file.id !== id));
|
setFiles(files.filter(file => file && file.id !== id));
|
||||||
},
|
},
|
||||||
[setFiles, files]
|
[setFiles, files],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -34,7 +34,7 @@ const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
|
||||||
onSortEnd={onMove}
|
onSortEnd={onMove}
|
||||||
items={files}
|
items={files}
|
||||||
locked={locked}
|
locked={locked}
|
||||||
size={!isMobile ? 220 : (innerWidth - 60) / 2}
|
size={!isTablet ? 220 : (innerWidth - 60) / 2}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from "react";
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { Anchor } from '~/components/common/Anchor';
|
import { Anchor } from "~/components/common/Anchor";
|
||||||
import { MenuDots } from '~/components/common/MenuDots';
|
import { MenuDots } from "~/components/common/MenuDots";
|
||||||
import { CellShade } from '~/components/flow/CellShade';
|
import { CellShade } from "~/components/flow/CellShade";
|
||||||
import { FlowCellImage } from '~/components/flow/FlowCellImage';
|
import { FlowCellImage } from "~/components/flow/FlowCellImage";
|
||||||
import { FlowCellMenu } from '~/components/flow/FlowCellMenu';
|
import { FlowCellMenu } from "~/components/flow/FlowCellMenu";
|
||||||
import { FlowCellText } from '~/components/flow/FlowCellText';
|
import { FlowCellText } from "~/components/flow/FlowCellText";
|
||||||
import { useClickOutsideFocus } from '~/hooks/dom/useClickOutsideFocus';
|
import { useClickOutsideFocus } from "~/hooks/dom/useClickOutsideFocus";
|
||||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
import { useWindowSize } from "~/hooks/dom/useWindowSize";
|
||||||
import { useFlowCellControls } from '~/hooks/flow/useFlowCellControls';
|
import { useFlowCellControls } from "~/hooks/flow/useFlowCellControls";
|
||||||
import { FlowDisplay, INode } from '~/types';
|
import { FlowDisplay, INode } from "~/types";
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: INode['id'];
|
id: INode["id"];
|
||||||
to: string;
|
to: string;
|
||||||
title: string;
|
title: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
|
@ -25,7 +25,7 @@ interface Props {
|
||||||
text?: string;
|
text?: string;
|
||||||
flow: FlowDisplay;
|
flow: FlowDisplay;
|
||||||
canEdit?: boolean;
|
canEdit?: boolean;
|
||||||
onChangeCellView: (id: INode['id'], flow: FlowDisplay) => void;
|
onChangeCellView: (id: INode["id"], flow: FlowDisplay) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FlowCell: FC<Props> = ({
|
const FlowCell: FC<Props> = ({
|
||||||
|
@ -39,10 +39,12 @@ const FlowCell: FC<Props> = ({
|
||||||
canEdit = false,
|
canEdit = false,
|
||||||
onChangeCellView,
|
onChangeCellView,
|
||||||
}) => {
|
}) => {
|
||||||
const { isMobile } = useWindowSize();
|
const { isTablet } = useWindowSize();
|
||||||
|
|
||||||
const withText =
|
const withText =
|
||||||
((!!flow.display && flow.display !== 'single') || !image) && flow.show_description && !!text;
|
((!!flow.display && flow.display !== "single") || !image) &&
|
||||||
|
flow.show_description &&
|
||||||
|
!!text;
|
||||||
const {
|
const {
|
||||||
hasDescription,
|
hasDescription,
|
||||||
setViewHorizontal,
|
setViewHorizontal,
|
||||||
|
@ -51,21 +53,26 @@ const FlowCell: FC<Props> = ({
|
||||||
setViewSingle,
|
setViewSingle,
|
||||||
toggleViewDescription,
|
toggleViewDescription,
|
||||||
} = useFlowCellControls(id, text, flow, onChangeCellView);
|
} = useFlowCellControls(id, text, flow, onChangeCellView);
|
||||||
const { isActive: isMenuActive, activate, ref, deactivate } = useClickOutsideFocus();
|
const {
|
||||||
|
isActive: isMenuActive,
|
||||||
|
activate,
|
||||||
|
ref,
|
||||||
|
deactivate,
|
||||||
|
} = useClickOutsideFocus();
|
||||||
|
|
||||||
const shadeSize = useMemo(() => {
|
const shadeSize = useMemo(() => {
|
||||||
const min = isMobile ? 10 : 15;
|
const min = isTablet ? 10 : 15;
|
||||||
const max = isMobile ? 20 : 40;
|
const max = isTablet ? 20 : 40;
|
||||||
|
|
||||||
return withText ? min : max;
|
return withText ? min : max;
|
||||||
}, [withText, isMobile]);
|
}, [withText, isTablet]);
|
||||||
|
|
||||||
const shadeAngle = useMemo(() => {
|
const shadeAngle = useMemo(() => {
|
||||||
if (flow.display === 'vertical') {
|
if (flow.display === "vertical") {
|
||||||
return 9;
|
return 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flow.display === 'horizontal') {
|
if (flow.display === "horizontal") {
|
||||||
return 15;
|
return 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +80,10 @@ const FlowCell: FC<Props> = ({
|
||||||
}, [flow.display]);
|
}, [flow.display]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.cell, styles[flow.display || 'single'])} ref={ref as any}>
|
<div
|
||||||
|
className={classNames(styles.cell, styles[flow.display || "single"])}
|
||||||
|
ref={ref as any}
|
||||||
|
>
|
||||||
{canEdit && !isMenuActive && (
|
{canEdit && !isMenuActive && (
|
||||||
<div className={styles.menu}>
|
<div className={styles.menu}>
|
||||||
<MenuDots onClick={activate} />
|
<MenuDots onClick={activate} />
|
||||||
|
@ -98,7 +108,10 @@ const FlowCell: FC<Props> = ({
|
||||||
|
|
||||||
<Anchor className={styles.link} href={to}>
|
<Anchor className={styles.link} href={to}>
|
||||||
{withText && (
|
{withText && (
|
||||||
<FlowCellText className={styles.text} heading={<h4 className={styles.title}>{title}</h4>}>
|
<FlowCellText
|
||||||
|
className={styles.text}
|
||||||
|
heading={<h4 className={styles.title}>{title}</h4>}
|
||||||
|
>
|
||||||
{text!}
|
{text!}
|
||||||
</FlowCellText>
|
</FlowCellText>
|
||||||
)}
|
)}
|
||||||
|
@ -113,7 +126,12 @@ const FlowCell: FC<Props> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!!title && (
|
{!!title && (
|
||||||
<CellShade color={color} className={styles.shade} size={shadeSize} angle={shadeAngle} />
|
<CellShade
|
||||||
|
color={color}
|
||||||
|
className={styles.shade}
|
||||||
|
size={shadeSize}
|
||||||
|
angle={shadeAngle}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!withText && (
|
{!withText && (
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import React, { FC, useCallback, useMemo, useState } from 'react';
|
import React, { FC, useCallback, useMemo, useState } from "react";
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from "classnames";
|
||||||
import SwiperCore, { Autoplay, EffectFade, Lazy, Navigation } from 'swiper';
|
import SwiperCore, { Autoplay, EffectFade, Lazy, Navigation } from "swiper";
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import SwiperClass from 'swiper/types/swiper-class';
|
import SwiperClass from "swiper/types/swiper-class";
|
||||||
|
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from "~/components/input/Icon";
|
||||||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
import { LoaderCircle } from "~/components/input/LoaderCircle";
|
||||||
import { ImagePresets, URLS } from '~/constants/urls';
|
import { ImagePresets, URLS } from "~/constants/urls";
|
||||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
import { useWindowSize } from "~/hooks/dom/useWindowSize";
|
||||||
import { useNavigation } from '~/hooks/navigation/useNavigation';
|
import { useNavigation } from "~/hooks/navigation/useNavigation";
|
||||||
import { IFlowNode } from '~/types';
|
import { IFlowNode } from "~/types";
|
||||||
import { getURLFromString } from '~/utils/dom';
|
import { getURLFromString } from "~/utils/dom";
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
SwiperCore.use([EffectFade, Lazy, Autoplay, Navigation]);
|
SwiperCore.use([EffectFade, Lazy, Autoplay, Navigation]);
|
||||||
|
|
||||||
|
@ -34,14 +34,17 @@ const lazy = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FlowSwiperHero: FC<Props> = ({ heroes }) => {
|
export const FlowSwiperHero: FC<Props> = ({ heroes }) => {
|
||||||
const { isMobile } = useWindowSize();
|
const { isTablet } = useWindowSize();
|
||||||
const { push } = useNavigation();
|
const { push } = useNavigation();
|
||||||
|
|
||||||
const [controlledSwiper, setControlledSwiper] = useState<SwiperClass | undefined>(undefined);
|
const [controlledSwiper, setControlledSwiper] = useState<
|
||||||
|
SwiperClass | undefined
|
||||||
|
>(undefined);
|
||||||
const [currentIndex, setCurrentIndex] = useState(heroes.length);
|
const [currentIndex, setCurrentIndex] = useState(heroes.length);
|
||||||
const preset = useMemo(() => (isMobile ? ImagePresets.cover : ImagePresets.small_hero), [
|
const preset = useMemo(
|
||||||
isMobile,
|
() => (isTablet ? ImagePresets.cover : ImagePresets.small_hero),
|
||||||
]);
|
[isTablet],
|
||||||
|
);
|
||||||
|
|
||||||
const onNext = useCallback(() => {
|
const onNext = useCallback(() => {
|
||||||
controlledSwiper?.slideNext(1);
|
controlledSwiper?.slideNext(1);
|
||||||
|
@ -79,7 +82,7 @@ export const FlowSwiperHero: FC<Props> = ({ heroes }) => {
|
||||||
(sw: SwiperClass) => {
|
(sw: SwiperClass) => {
|
||||||
push(URLS.NODE_URL(heroes[sw.realIndex]?.id));
|
push(URLS.NODE_URL(heroes[sw.realIndex]?.id));
|
||||||
},
|
},
|
||||||
[push, heroes]
|
[push, heroes],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!heroes.length) {
|
if (!heroes.length) {
|
||||||
|
@ -135,7 +138,7 @@ export const FlowSwiperHero: FC<Props> = ({ heroes }) => {
|
||||||
<img
|
<img
|
||||||
src={getURLFromString(node.thumbnail!, preset)}
|
src={getURLFromString(node.thumbnail!, preset)}
|
||||||
alt=""
|
alt=""
|
||||||
className={classNames(styles.preview, 'swiper-lazy')}
|
className={classNames(styles.preview, "swiper-lazy")}
|
||||||
/>
|
/>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React, { VFC } from 'react';
|
import React, { VFC } from "react";
|
||||||
|
|
||||||
import Tippy from '@tippyjs/react';
|
import Tippy from "@tippyjs/react";
|
||||||
|
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from "~/components/input/Icon";
|
||||||
import { MenuButton, MenuItemWithIcon, SeparatedMenu } from '~/components/menu';
|
import { MenuButton, MenuItemWithIcon, SeparatedMenu } from "~/components/menu";
|
||||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
import { useWindowSize } from "~/hooks/dom/useWindowSize";
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
interface NodeEditMenuProps {
|
interface NodeEditMenuProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -30,17 +30,20 @@ const NodeEditMenu: VFC<NodeEditMenuProps> = ({
|
||||||
onLock,
|
onLock,
|
||||||
onEdit,
|
onEdit,
|
||||||
}) => {
|
}) => {
|
||||||
const { isMobile } = useWindowSize();
|
const { isTablet } = useWindowSize();
|
||||||
|
|
||||||
if (isMobile) {
|
if (isTablet) {
|
||||||
return (
|
return (
|
||||||
<MenuButton
|
<MenuButton
|
||||||
icon={<Icon icon="dots-vertical" className={styles.icon} size={24} />}
|
icon={<Icon icon="dots-vertical" className={styles.icon} size={24} />}
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
{canStar && (
|
{canStar && (
|
||||||
<MenuItemWithIcon icon={isHeroic ? 'star_full' : 'star'} onClick={onStar}>
|
<MenuItemWithIcon
|
||||||
{isHeroic ? 'Убрать с главной' : 'На главную'}
|
icon={isHeroic ? "star_full" : "star"}
|
||||||
|
onClick={onStar}
|
||||||
|
>
|
||||||
|
{isHeroic ? "Убрать с главной" : "На главную"}
|
||||||
</MenuItemWithIcon>
|
</MenuItemWithIcon>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -48,8 +51,11 @@ const NodeEditMenu: VFC<NodeEditMenuProps> = ({
|
||||||
Редактировать
|
Редактировать
|
||||||
</MenuItemWithIcon>
|
</MenuItemWithIcon>
|
||||||
|
|
||||||
<MenuItemWithIcon icon={isLocked ? 'locked' : 'unlocked'} onClick={onLock}>
|
<MenuItemWithIcon
|
||||||
{isLocked ? 'Восстановить' : 'Удалить'}
|
icon={isLocked ? "locked" : "unlocked"}
|
||||||
|
onClick={onLock}
|
||||||
|
>
|
||||||
|
{isLocked ? "Восстановить" : "Удалить"}
|
||||||
</MenuItemWithIcon>
|
</MenuItemWithIcon>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
);
|
);
|
||||||
|
@ -58,9 +64,9 @@ const NodeEditMenu: VFC<NodeEditMenuProps> = ({
|
||||||
return (
|
return (
|
||||||
<SeparatedMenu>
|
<SeparatedMenu>
|
||||||
{canStar && (
|
{canStar && (
|
||||||
<Tippy content={isHeroic ? 'Убрать с главной' : 'На главную'}>
|
<Tippy content={isHeroic ? "Убрать с главной" : "На главную"}>
|
||||||
<button className={className} onClick={onStar}>
|
<button className={className} onClick={onStar}>
|
||||||
<Icon icon={isHeroic ? 'star_full' : 'star'} size={24} />
|
<Icon icon={isHeroic ? "star_full" : "star"} size={24} />
|
||||||
</button>
|
</button>
|
||||||
</Tippy>
|
</Tippy>
|
||||||
)}
|
)}
|
||||||
|
@ -71,9 +77,9 @@ const NodeEditMenu: VFC<NodeEditMenuProps> = ({
|
||||||
</button>
|
</button>
|
||||||
</Tippy>
|
</Tippy>
|
||||||
|
|
||||||
<Tippy content={isLocked ? 'Восстановить' : 'Удалить'}>
|
<Tippy content={isLocked ? "Восстановить" : "Удалить"}>
|
||||||
<button className={className} onClick={onLock}>
|
<button className={className} onClick={onLock}>
|
||||||
<Icon icon={isLocked ? 'locked' : 'unlocked'} size={24} />
|
<Icon icon={isLocked ? "locked" : "unlocked"} size={24} />
|
||||||
</button>
|
</button>
|
||||||
</Tippy>
|
</Tippy>
|
||||||
</SeparatedMenu>
|
</SeparatedMenu>
|
||||||
|
|
22
src/containers/auth/SuperPowersToggle/index.tsx
Normal file
22
src/containers/auth/SuperPowersToggle/index.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
|
import { BorisSuperpowers } from "~/components/boris/BorisSuperpowers";
|
||||||
|
import { useAuth } from "~/hooks/auth/useAuth";
|
||||||
|
import { useSuperPowers } from "~/hooks/auth/useSuperPowers";
|
||||||
|
|
||||||
|
interface SuperPowersToggleProps {}
|
||||||
|
|
||||||
|
const SuperPowersToggle: FC<SuperPowersToggleProps> = observer(() => {
|
||||||
|
const { isUser } = useAuth();
|
||||||
|
const { isTester, setIsTester } = useSuperPowers();
|
||||||
|
|
||||||
|
if (!isUser) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BorisSuperpowers active={isTester} onChange={setIsTester} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
export { SuperPowersToggle };
|
30
src/containers/boris/BorisSidebar/index.tsx
Normal file
30
src/containers/boris/BorisSidebar/index.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
|
import { BorisContacts } from "~/components/boris/BorisContacts";
|
||||||
|
import { BorisStats } from "~/components/boris/BorisStats";
|
||||||
|
import { Group } from "~/components/containers/Group";
|
||||||
|
import { SuperPowersToggle } from "~/containers/auth/SuperPowersToggle";
|
||||||
|
import styles from "~/layouts/BorisLayout/styles.module.scss";
|
||||||
|
import { BorisUsageStats } from "~/types/boris";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isUser: boolean;
|
||||||
|
stats: BorisUsageStats;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BorisSidebar: FC<Props> = ({ isUser, stats, isLoading }) => (
|
||||||
|
<Group className={styles.stats__container}>
|
||||||
|
<div className={styles.super_powers}>
|
||||||
|
<SuperPowersToggle />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BorisContacts />
|
||||||
|
|
||||||
|
<div className={styles.stats__wrap}>
|
||||||
|
<BorisStats stats={stats} isLoading={isLoading} />
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { BorisSidebar };
|
|
@ -1,18 +1,18 @@
|
||||||
import React, { useEffect, useRef, VFC } from 'react';
|
import React, { useEffect, useRef, VFC } from "react";
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from "classnames";
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from "mobx-react-lite";
|
||||||
import PhotoSwipeUI_Default from 'photoswipe/dist/photoswipe-ui-default.js';
|
import PhotoSwipeUI_Default from "photoswipe/dist/photoswipe-ui-default.js";
|
||||||
import PhotoSwipeJs from 'photoswipe/dist/photoswipe.js';
|
import PhotoSwipeJs from "photoswipe/dist/photoswipe.js";
|
||||||
|
|
||||||
import { ImagePresets } from '~/constants/urls';
|
import { ImagePresets } from "~/constants/urls";
|
||||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
import { useWindowSize } from "~/hooks/dom/useWindowSize";
|
||||||
import { useModal } from '~/hooks/modal/useModal';
|
import { useModal } from "~/hooks/modal/useModal";
|
||||||
import { IFile } from '~/types';
|
import { IFile } from "~/types";
|
||||||
import { DialogComponentProps } from '~/types/modal';
|
import { DialogComponentProps } from "~/types/modal";
|
||||||
import { getURL } from '~/utils/dom';
|
import { getURL } from "~/utils/dom";
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
export interface PhotoSwipeProps extends DialogComponentProps {
|
export interface PhotoSwipeProps extends DialogComponentProps {
|
||||||
items: IFile[];
|
items: IFile[];
|
||||||
|
@ -22,7 +22,7 @@ export interface PhotoSwipeProps extends DialogComponentProps {
|
||||||
const PhotoSwipe: VFC<PhotoSwipeProps> = observer(({ index, items }) => {
|
const PhotoSwipe: VFC<PhotoSwipeProps> = observer(({ index, items }) => {
|
||||||
let ref = useRef<HTMLDivElement>(null);
|
let ref = useRef<HTMLDivElement>(null);
|
||||||
const { hideModal } = useModal();
|
const { hideModal } = useModal();
|
||||||
const { isMobile } = useWindowSize();
|
const { isTablet } = useWindowSize();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
new Promise(async resolve => {
|
new Promise(async resolve => {
|
||||||
|
@ -34,7 +34,10 @@ const PhotoSwipe: VFC<PhotoSwipeProps> = observer(({ index, items }) => {
|
||||||
|
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
resolveImage({
|
resolveImage({
|
||||||
src: getURL(image, isMobile ? ImagePresets[900] : ImagePresets[1600]),
|
src: getURL(
|
||||||
|
image,
|
||||||
|
isTablet ? ImagePresets[900] : ImagePresets[1600],
|
||||||
|
),
|
||||||
h: img.naturalHeight,
|
h: img.naturalHeight,
|
||||||
w: img.naturalWidth,
|
w: img.naturalWidth,
|
||||||
});
|
});
|
||||||
|
@ -45,8 +48,8 @@ const PhotoSwipe: VFC<PhotoSwipeProps> = observer(({ index, items }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
img.src = getURL(image, ImagePresets[1600]);
|
img.src = getURL(image, ImagePresets[1600]);
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
resolve(images);
|
resolve(images);
|
||||||
|
@ -58,15 +61,21 @@ const PhotoSwipe: VFC<PhotoSwipeProps> = observer(({ index, items }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
ps.init();
|
ps.init();
|
||||||
ps.listen('destroy', hideModal);
|
ps.listen("destroy", hideModal);
|
||||||
ps.listen('close', hideModal);
|
ps.listen("close", hideModal);
|
||||||
});
|
});
|
||||||
}, [hideModal, items, index, isMobile]);
|
}, [hideModal, items, index, isTablet]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pswp" tabIndex={-1} role="dialog" aria-hidden="true" ref={ref}>
|
<div
|
||||||
<div className={classNames('pswp__bg', styles.bg)} />
|
className="pswp"
|
||||||
<div className={classNames('pswp__scroll-wrap', styles.wrap)}>
|
tabIndex={-1}
|
||||||
|
role="dialog"
|
||||||
|
aria-hidden="true"
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<div className={classNames("pswp__bg", styles.bg)} />
|
||||||
|
<div className={classNames("pswp__scroll-wrap", styles.wrap)}>
|
||||||
<div className="pswp__container">
|
<div className="pswp__container">
|
||||||
<div className="pswp__item" />
|
<div className="pswp__item" />
|
||||||
<div className="pswp__item" />
|
<div className="pswp__item" />
|
||||||
|
@ -74,9 +83,12 @@ const PhotoSwipe: VFC<PhotoSwipeProps> = observer(({ index, items }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pswp__ui pswp__ui--hidden">
|
<div className="pswp__ui pswp__ui--hidden">
|
||||||
<div className={classNames('pswp__top-bar', styles.bar)}>
|
<div className={classNames("pswp__top-bar", styles.bar)}>
|
||||||
<div className="pswp__counter" />
|
<div className="pswp__counter" />
|
||||||
<button className="pswp__button pswp__button--close" title="Close (Esc)" />
|
<button
|
||||||
|
className="pswp__button pswp__button--close"
|
||||||
|
title="Close (Esc)"
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="pswp__preloader">
|
<div className="pswp__preloader">
|
||||||
<div className="pswp__preloader__icn">
|
<div className="pswp__preloader__icn">
|
||||||
|
@ -96,7 +108,10 @@ const PhotoSwipe: VFC<PhotoSwipeProps> = observer(({ index, items }) => {
|
||||||
title="Previous (arrow left)"
|
title="Previous (arrow left)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button className="pswp__button pswp__button--arrow--right" title="Next (arrow right)" />
|
<button
|
||||||
|
className="pswp__button pswp__button--arrow--right"
|
||||||
|
title="Next (arrow right)"
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="pswp__caption">
|
<div className="pswp__caption">
|
||||||
<div className="pswp__caption__center" />
|
<div className="pswp__caption__center" />
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { useAuth } from "~/hooks/auth/useAuth";
|
||||||
import markdown from "~/styles/common/markdown.module.scss";
|
import markdown from "~/styles/common/markdown.module.scss";
|
||||||
|
|
||||||
import { ProfileSidebarLogoutButton } from "../ProfileSidebarLogoutButton";
|
import { ProfileSidebarLogoutButton } from "../ProfileSidebarLogoutButton";
|
||||||
|
import { ProfileToggles } from "../ProfileToggles";
|
||||||
|
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
|
@ -49,6 +50,10 @@ const ProfileSidebarMenu: VFC<ProfileSidebarMenuProps> = ({ onClose }) => {
|
||||||
</VerticalMenu.Item>
|
</VerticalMenu.Item>
|
||||||
</VerticalMenu>
|
</VerticalMenu>
|
||||||
|
|
||||||
|
<div className={styles.toggles}>
|
||||||
|
<ProfileToggles />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.stats}>
|
<div className={styles.stats}>
|
||||||
<ProfileStats />
|
<ProfileStats />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,3 +19,7 @@
|
||||||
.stats {
|
.stats {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggles {
|
||||||
|
padding-top: $gap * 2;
|
||||||
|
}
|
||||||
|
|
17
src/containers/profile/ProfileToggles/index.tsx
Normal file
17
src/containers/profile/ProfileToggles/index.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
|
||||||
|
import { Group } from "~/components/containers/Group";
|
||||||
|
import { Zone } from "~/components/containers/Zone";
|
||||||
|
import { SuperPowersToggle } from "~/containers/auth/SuperPowersToggle";
|
||||||
|
|
||||||
|
interface ProfileTogglesProps {}
|
||||||
|
|
||||||
|
const ProfileToggles: FC<ProfileTogglesProps> = () => (
|
||||||
|
<Zone>
|
||||||
|
<Group>
|
||||||
|
<SuperPowersToggle />
|
||||||
|
</Group>
|
||||||
|
</Zone>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { ProfileToggles };
|
|
@ -8,6 +8,7 @@ import { InputText } from "~/components/input/InputText";
|
||||||
import { Textarea } from "~/components/input/Textarea";
|
import { Textarea } from "~/components/input/Textarea";
|
||||||
import { ERROR_LITERAL } from "~/constants/errors";
|
import { ERROR_LITERAL } from "~/constants/errors";
|
||||||
import { ProfileAccounts } from "~/containers/profile/ProfileAccounts";
|
import { ProfileAccounts } from "~/containers/profile/ProfileAccounts";
|
||||||
|
import { useWindowSize } from "~/hooks/dom/useWindowSize";
|
||||||
import { useSettings } from "~/utils/providers/SettingsProvider";
|
import { useSettings } from "~/utils/providers/SettingsProvider";
|
||||||
import { has } from "~/utils/ramda";
|
import { has } from "~/utils/ramda";
|
||||||
|
|
||||||
|
@ -20,10 +21,11 @@ const getError = (error?: string) =>
|
||||||
|
|
||||||
const UserSettingsView: FC<UserSettingsViewProps> = () => {
|
const UserSettingsView: FC<UserSettingsViewProps> = () => {
|
||||||
const { values, handleChange, errors } = useSettings();
|
const { values, handleChange, errors } = useSettings();
|
||||||
|
const { isPhone } = useWindowSize();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<Group horizontal className={styles.base_info}>
|
<Group horizontal={!isPhone} className={styles.base_info}>
|
||||||
<Superpower>
|
<Superpower>
|
||||||
<Zone className={styles.avatar} title="Фото">
|
<Zone className={styles.avatar} title="Фото">
|
||||||
<small>
|
<small>
|
||||||
|
@ -33,7 +35,7 @@ const UserSettingsView: FC<UserSettingsViewProps> = () => {
|
||||||
</Zone>
|
</Zone>
|
||||||
</Superpower>
|
</Superpower>
|
||||||
|
|
||||||
<Zone title="О себе">
|
<Zone title="О себе" className={styles.about}>
|
||||||
<Group>
|
<Group>
|
||||||
<InputText
|
<InputText
|
||||||
value={values.fullname}
|
value={values.fullname}
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
$pad_danger: mix($red, $content_bg, 70%);
|
$pad_danger: mix($red, $content_bg, 70%);
|
||||||
$pad_usual: mix(white, $content_bg, 10%);
|
$pad_usual: mix(white, $content_bg, 10%);
|
||||||
|
|
||||||
|
.about {
|
||||||
|
flex: 4;
|
||||||
|
}
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
padding: $gap;
|
padding: $gap;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
|
@ -21,5 +25,5 @@ div.base_info.base_info {
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
flex: 0 0 150px;
|
flex: 1 0 90px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useCallback, useMemo, VFC } from "react";
|
import React, { useCallback, useEffect, useMemo, VFC } from "react";
|
||||||
|
|
||||||
import { isNil } from "ramda";
|
import { isNil } from "ramda";
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import { SidebarStackCard } from "~/components/sidebar/SidebarStackCard";
|
||||||
import { SidebarName } from "~/constants/sidebar";
|
import { SidebarName } from "~/constants/sidebar";
|
||||||
import { ProfileSidebarMenu } from "~/containers/profile/ProfileSidebarMenu";
|
import { ProfileSidebarMenu } from "~/containers/profile/ProfileSidebarMenu";
|
||||||
import { SidebarWrapper } from "~/containers/sidebars/SidebarWrapper";
|
import { SidebarWrapper } from "~/containers/sidebars/SidebarWrapper";
|
||||||
|
import { useAuth } from "~/hooks/auth/useAuth";
|
||||||
import type { SidebarComponentProps } from "~/types/sidebar";
|
import type { SidebarComponentProps } from "~/types/sidebar";
|
||||||
|
|
||||||
const tabs = ["profile", "bookmarks"] as const;
|
const tabs = ["profile", "bookmarks"] as const;
|
||||||
|
@ -24,6 +25,8 @@ const ProfileSidebar: VFC<ProfileSidebarProps> = ({
|
||||||
page,
|
page,
|
||||||
openSidebar,
|
openSidebar,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isUser } = useAuth();
|
||||||
|
|
||||||
const tab = useMemo(
|
const tab = useMemo(
|
||||||
() => (page ? Math.max(tabs.indexOf(page), 0) : undefined),
|
() => (page ? Math.max(tabs.indexOf(page), 0) : undefined),
|
||||||
[page],
|
[page],
|
||||||
|
@ -38,6 +41,16 @@ const ProfileSidebar: VFC<ProfileSidebarProps> = ({
|
||||||
[open, onRequestClose],
|
[open, onRequestClose],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isUser) {
|
||||||
|
onRequestClose();
|
||||||
|
}
|
||||||
|
}, [isUser]);
|
||||||
|
|
||||||
|
if (!isUser) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarWrapper onClose={onRequestClose}>
|
<SidebarWrapper onClose={onRequestClose}>
|
||||||
<SidebarStack tab={tab} onTabChange={onTabChange}>
|
<SidebarStack tab={tab} onTabChange={onTabChange}>
|
||||||
|
|
9
src/hooks/auth/useSuperPowers.ts
Normal file
9
src/hooks/auth/useSuperPowers.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
import { useAuth } from "~/hooks/auth/useAuth";
|
||||||
|
|
||||||
|
export const useSuperPowers = () => {
|
||||||
|
const { isTester, setIsTester } = useAuth();
|
||||||
|
|
||||||
|
return useMemo(() => ({ isTester, setIsTester }), [isTester, setIsTester]);
|
||||||
|
};
|
|
@ -1,18 +1,16 @@
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
import isBefore from 'date-fns/isBefore';
|
import isBefore from "date-fns/isBefore";
|
||||||
|
|
||||||
import { useRandomPhrase } from '~/constants/phrases';
|
import { useRandomPhrase } from "~/constants/phrases";
|
||||||
import { useAuth } from '~/hooks/auth/useAuth';
|
import { useLastSeenBoris } from "~/hooks/auth/useLastSeenBoris";
|
||||||
import { useLastSeenBoris } from '~/hooks/auth/useLastSeenBoris';
|
import { useBorisStats } from "~/hooks/boris/useBorisStats";
|
||||||
import { useBorisStats } from '~/hooks/boris/useBorisStats';
|
import { IComment } from "~/types";
|
||||||
import { IComment } from '~/types';
|
|
||||||
|
|
||||||
export const useBoris = (comments: IComment[]) => {
|
export const useBoris = (comments: IComment[]) => {
|
||||||
const title = useRandomPhrase('BORIS_TITLE');
|
const title = useRandomPhrase("BORIS_TITLE");
|
||||||
|
|
||||||
const { lastSeen, setLastSeen } = useLastSeenBoris();
|
const { lastSeen, setLastSeen } = useLastSeenBoris();
|
||||||
const { isTester, setIsTester } = useAuth();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const last_comment = comments[0];
|
const last_comment = comments[0];
|
||||||
|
@ -32,12 +30,5 @@ export const useBoris = (comments: IComment[]) => {
|
||||||
|
|
||||||
const { stats, isLoading: isLoadingStats } = useBorisStats();
|
const { stats, isLoading: isLoadingStats } = useBorisStats();
|
||||||
|
|
||||||
const setIsBetaTester = useCallback(
|
return { stats, title, isLoadingStats };
|
||||||
(isTester: boolean) => {
|
|
||||||
setIsTester(isTester);
|
|
||||||
},
|
|
||||||
[setIsTester]
|
|
||||||
);
|
|
||||||
|
|
||||||
return { setIsBetaTester, isTester, stats, title, isLoadingStats };
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,24 +1,30 @@
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
export const useWindowSize = () => {
|
export const useWindowSize = () => {
|
||||||
const [size, setSize] = useState({ innerWidth: 0, innerHeight: 0, isMobile: false });
|
const [size, setSize] = useState({
|
||||||
|
innerWidth: 0,
|
||||||
|
innerHeight: 0,
|
||||||
|
isTablet: false,
|
||||||
|
isPhone: false,
|
||||||
|
});
|
||||||
|
|
||||||
const onResize = useCallback(
|
const onResize = useCallback(
|
||||||
() =>
|
() =>
|
||||||
setSize({
|
setSize({
|
||||||
innerWidth: window.innerWidth,
|
innerWidth: window.innerWidth,
|
||||||
innerHeight: window.innerHeight,
|
innerHeight: window.innerHeight,
|
||||||
isMobile: window.innerWidth < 768,
|
isTablet: window.innerWidth < 768,
|
||||||
|
isPhone: window.innerWidth < 500,
|
||||||
}),
|
}),
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onResize();
|
onResize();
|
||||||
|
|
||||||
window.addEventListener('resize', onResize);
|
window.addEventListener("resize", onResize);
|
||||||
|
|
||||||
return () => window.removeEventListener('resize', onResize);
|
return () => window.removeEventListener("resize", onResize);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
|
|
|
@ -1,89 +1,85 @@
|
||||||
import { FC, useMemo } from 'react';
|
import { FC, useMemo } from "react";
|
||||||
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
import { BorisGraphicStats } from '~/components/boris/BorisGraphicStats';
|
import { BorisGraphicStats } from "~/components/boris/BorisGraphicStats";
|
||||||
import { BorisSidebar } from '~/components/boris/BorisSidebar';
|
import { Superpower } from "~/components/boris/Superpower";
|
||||||
import { Superpower } from '~/components/boris/Superpower';
|
import { Card } from "~/components/containers/Card";
|
||||||
import { Card } from '~/components/containers/Card';
|
import { Group } from "~/components/containers/Group";
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Sticky } from "~/components/containers/Sticky";
|
||||||
import { Sticky } from '~/components/containers/Sticky';
|
import { BorisComments } from "~/containers/boris/BorisComments";
|
||||||
import { BorisComments } from '~/containers/boris/BorisComments';
|
import { BorisSidebar } from "~/containers/boris/BorisSidebar";
|
||||||
import { BorisSuperPowersSSR } from '~/containers/boris/BorisSuperpowers/ssr';
|
import { BorisSuperPowersSSR } from "~/containers/boris/BorisSuperpowers/ssr";
|
||||||
import { Container } from '~/containers/main/Container';
|
import { Container } from "~/containers/main/Container";
|
||||||
import { SidebarRouter } from '~/containers/main/SidebarRouter';
|
import { SidebarRouter } from "~/containers/main/SidebarRouter";
|
||||||
import { BorisUsageStats } from '~/types/boris';
|
import { BorisUsageStats } from "~/types/boris";
|
||||||
import { useAuthProvider } from '~/utils/providers/AuthProvider';
|
import { useAuthProvider } from "~/utils/providers/AuthProvider";
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
title: string;
|
title: string;
|
||||||
setIsBetaTester: (val: boolean) => void;
|
|
||||||
isTester: boolean;
|
|
||||||
stats: BorisUsageStats;
|
stats: BorisUsageStats;
|
||||||
isLoadingStats: boolean;
|
isLoadingStats: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BorisLayout: FC<IProps> = observer(
|
const BorisLayout: FC<IProps> = observer(({ title, stats, isLoadingStats }) => {
|
||||||
({ title, setIsBetaTester, isTester, stats, isLoadingStats }) => {
|
const { isUser } = useAuthProvider();
|
||||||
const { isUser } = useAuthProvider();
|
const commentsByMonth = useMemo(
|
||||||
const commentsByMonth = useMemo(() => stats.backend.comments.by_month?.slice(0, -1), [
|
() => stats.backend.comments.by_month?.slice(0, -1),
|
||||||
stats.backend.comments.by_month,
|
[stats.backend.comments.by_month],
|
||||||
]);
|
);
|
||||||
const nodesByMonth = useMemo(() => stats.backend.nodes.by_month?.slice(0, -1), [
|
const nodesByMonth = useMemo(
|
||||||
stats.backend.comments.by_month,
|
() => stats.backend.nodes.by_month?.slice(0, -1),
|
||||||
]);
|
[stats.backend.comments.by_month],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<div className={styles.cover} />
|
<div className={styles.cover} />
|
||||||
|
|
||||||
<div className={styles.image}>
|
<div className={styles.image}>
|
||||||
<div className={styles.caption}>
|
<div className={styles.caption}>
|
||||||
<div className={styles.caption_text}>{title}</div>
|
<div className={styles.caption_text}>{title}</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<img src="/images/boris_robot.svg" alt="Борис" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.container}>
|
<img src="/images/boris_robot.svg" alt="Борис" />
|
||||||
<Card className={styles.content}>
|
|
||||||
<Group>
|
|
||||||
<Superpower>
|
|
||||||
<BorisSuperPowersSSR />
|
|
||||||
</Superpower>
|
|
||||||
|
|
||||||
<BorisGraphicStats
|
|
||||||
totalComments={stats.backend.comments.total}
|
|
||||||
commentsByMonth={commentsByMonth}
|
|
||||||
totalNodes={stats.backend.nodes.total}
|
|
||||||
nodesByMonth={nodesByMonth}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<BorisComments />
|
|
||||||
</Group>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Group className={styles.stats}>
|
|
||||||
<Sticky>
|
|
||||||
<BorisSidebar
|
|
||||||
isTester={isTester}
|
|
||||||
stats={stats}
|
|
||||||
setBetaTester={setIsBetaTester}
|
|
||||||
isUser={isUser}
|
|
||||||
isLoading={isLoadingStats}
|
|
||||||
/>
|
|
||||||
</Sticky>
|
|
||||||
</Group>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SidebarRouter prefix="/" />
|
<div className={styles.container}>
|
||||||
</Container>
|
<Card className={styles.content}>
|
||||||
);
|
<Group>
|
||||||
}
|
<Superpower>
|
||||||
);
|
<BorisSuperPowersSSR />
|
||||||
|
</Superpower>
|
||||||
|
|
||||||
|
<BorisGraphicStats
|
||||||
|
totalComments={stats.backend.comments.total}
|
||||||
|
commentsByMonth={commentsByMonth}
|
||||||
|
totalNodes={stats.backend.nodes.total}
|
||||||
|
nodesByMonth={nodesByMonth}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BorisComments />
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Group className={styles.stats}>
|
||||||
|
<Sticky>
|
||||||
|
<BorisSidebar
|
||||||
|
stats={stats}
|
||||||
|
isUser={isUser}
|
||||||
|
isLoading={isLoadingStats}
|
||||||
|
/>
|
||||||
|
</Sticky>
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SidebarRouter prefix="/" />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export { BorisLayout };
|
export { BorisLayout };
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import React, { VFC } from 'react';
|
import React, { VFC } from "react";
|
||||||
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
import { PageTitle } from '~/components/common/PageTitle';
|
import { PageTitle } from "~/components/common/PageTitle";
|
||||||
import { useBoris } from '~/hooks/boris/useBoris';
|
import { useBoris } from "~/hooks/boris/useBoris";
|
||||||
import { useNodeComments } from '~/hooks/comments/useNodeComments';
|
import { useNodeComments } from "~/hooks/comments/useNodeComments";
|
||||||
import { useImageModal } from '~/hooks/navigation/useImageModal';
|
import { useImageModal } from "~/hooks/navigation/useImageModal";
|
||||||
import { useLoadNode } from '~/hooks/node/useLoadNode';
|
import { useLoadNode } from "~/hooks/node/useLoadNode";
|
||||||
import { BorisLayout } from '~/layouts/BorisLayout';
|
import { BorisLayout } from "~/layouts/BorisLayout";
|
||||||
import { CommentContextProvider } from '~/utils/context/CommentContextProvider';
|
import { CommentContextProvider } from "~/utils/context/CommentContextProvider";
|
||||||
import { NodeContextProvider } from '~/utils/context/NodeContextProvider';
|
import { NodeContextProvider } from "~/utils/context/NodeContextProvider";
|
||||||
import { getPageTitle } from '~/utils/ssr/getPageTitle';
|
import { getPageTitle } from "~/utils/ssr/getPageTitle";
|
||||||
|
|
||||||
const BorisPage: VFC = observer(() => {
|
const BorisPage: VFC = observer(() => {
|
||||||
const { node, isLoading, update } = useLoadNode(696);
|
const { node, isLoading, update } = useLoadNode(696);
|
||||||
|
@ -25,7 +25,7 @@ const BorisPage: VFC = observer(() => {
|
||||||
isLoading: isLoadingComments,
|
isLoading: isLoadingComments,
|
||||||
isLoadingMore,
|
isLoadingMore,
|
||||||
} = useNodeComments(696);
|
} = useNodeComments(696);
|
||||||
const { title, setIsBetaTester, isTester, stats, isLoadingStats } = useBoris(comments);
|
const { title, stats, isLoadingStats } = useBoris(comments);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeContextProvider node={node} isLoading={isLoading} update={update}>
|
<NodeContextProvider node={node} isLoading={isLoading} update={update}>
|
||||||
|
@ -39,12 +39,10 @@ const BorisPage: VFC = observer(() => {
|
||||||
onLoadMoreComments={onLoadMoreComments}
|
onLoadMoreComments={onLoadMoreComments}
|
||||||
onDeleteComment={onDeleteComment}
|
onDeleteComment={onDeleteComment}
|
||||||
>
|
>
|
||||||
<PageTitle title={getPageTitle('Борис')} />
|
<PageTitle title={getPageTitle("Борис")} />
|
||||||
|
|
||||||
<BorisLayout
|
<BorisLayout
|
||||||
title={title}
|
title={title}
|
||||||
setIsBetaTester={setIsBetaTester}
|
|
||||||
isTester={isTester}
|
|
||||||
stats={stats}
|
stats={stats}
|
||||||
isLoadingStats={isLoadingStats}
|
isLoadingStats={isLoadingStats}
|
||||||
/>
|
/>
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue