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