From 5d34090238b242182e06d506760dd1b2b1133549 Mon Sep 17 00:00:00 2001 From: muerwre <33246675+muerwre@users.noreply.github.com> Date: Fri, 12 Aug 2022 14:07:19 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BA=D0=B8=20=D0=B2=20?= =?UTF-8?q?=D1=81=D0=B0=D0=B9=D0=B4=D0=B1=D0=B0=D1=80=20(#126)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added notes sidebar * added note dropping and editing * added sidebar navigation * handling sidebarchanges over time * using router back for closing sidebar * fixed tripping inside single sidebar * added superpowers toggle to sidebar * user button opens sidebar now * added profile cover for profile sidebar * removed profile sidebar completely * ran prettier over project * added note not found error literal --- .env.local | 8 +- .gitignore | 4 +- package.json | 2 +- src/api/notes/index.ts | 31 ++++- src/api/notes/types.ts | 13 +- .../auth/login/LoginScene/index.tsx | 34 ++--- src/components/boris/Superpower/index.tsx | 6 +- src/components/containers/Markdown/index.tsx | 16 ++- src/components/containers/Zone/index.tsx | 16 ++- .../containers/Zone/styles.module.scss | 8 +- .../editors/EditorButtons/index.tsx | 6 +- src/components/editors/ImageGrid/index.tsx | 8 +- src/components/flow/FlowCell/index.tsx | 36 ++++-- src/components/flow/FlowCellText/index.tsx | 6 +- src/components/flow/FlowSwiperHero/index.tsx | 15 ++- src/components/lab/LabDescription/index.tsx | 8 +- src/components/lab/LabText/index.tsx | 15 +-- src/components/main/UserButton/index.tsx | 47 +++---- src/components/node/NodeAuthorBlock/index.tsx | 5 +- src/components/node/NodeEditMenu/index.tsx | 14 +- src/components/notes/NoteCard/index.tsx | 54 ++++++-- .../notes/NoteCard/styles.module.scss | 4 - .../notes/NoteCreationForm/index.tsx | 99 +++++++++++++++ .../notes/NoteCreationForm/styles.module.scss | 11 ++ .../profile/ProfileSettings/index.tsx | 14 +- .../profile/ProfileSidebarNotes/index.tsx | 15 ++- .../profile/ProfileSidebarSettings/index.tsx | 18 +-- src/components/sidebar/SidebarStack/index.tsx | 42 +++++- src/constants/errors.ts | 11 +- src/constants/events.ts | 1 - src/constants/modal/index.ts | 6 - src/constants/sidebar/components.ts | 9 ++ src/constants/sidebar/index.ts | 8 +- src/constants/urls.ts | 55 ++++---- .../auth/SuperPowersToggle/index.tsx | 22 ++++ .../boris/BorisSidebar/index.tsx | 10 +- .../boris/BorisSuperpowers/index.tsx | 33 +---- src/containers/dialogs/LoginDialog/index.tsx | 38 +++--- src/containers/dialogs/PhotoSwipe/index.tsx | 31 +++-- .../dialogs/ProfileDialog/index.tsx | 47 ------- .../dialogs/ProfileDialog/styles.module.scss | 5 - src/containers/main/Header/index.tsx | 35 +++-- .../profile/ProfilePageLeft/index.tsx | 12 +- .../profile/ProfileSidebarMenu/index.tsx | 15 ++- .../ProfileSidebarMenu/styles.module.scss | 4 + .../profile/ProfileToggles/index.tsx | 17 +++ .../settings/SettingsNotes/index.tsx | 98 ++++++++------ .../settings/SettingsNotes/styles.module.scss | 26 ++++ .../settings/UserSettingsView/index.tsx | 42 +++--- .../UserSettingsView/styles.module.scss | 8 +- .../sidebars/ProfileSidebar/index.tsx | 66 ++++++++-- .../sidebars/SidebarWrapper/index.tsx | 17 ++- src/containers/tags/TagInput/index.tsx | 33 +++-- src/hooks/auth/useMessageEventReactions.ts | 8 -- src/hooks/auth/useSuperPowers.ts | 9 ++ src/hooks/boris/useBoris.ts | 25 ++-- src/hooks/dom/useConfirmation.ts | 11 ++ src/hooks/dom/useWindowSize.ts | 18 ++- src/hooks/notes/useGetNotes.ts | 55 -------- src/hooks/notes/useNotes.ts | 113 +++++++++++++++++ src/layouts/BorisLayout/index.tsx | 102 ++++++++------- src/pages/boris.tsx | 4 +- src/styles/_global.scss | 21 +-- src/styles/_reset.scss | 120 +++++++++++++++--- src/types/index.ts | 35 +++-- src/types/sidebar/index.ts | 20 +++ src/utils/formatText.ts | 20 +-- src/utils/providers/NoteProvider.tsx | 21 +++ src/utils/providers/SidebarProvider.tsx | 100 +++++++++------ src/utils/user.ts | 9 -- tsconfig.tsbuildinfo | 2 +- yarn.lock | 8 +- 72 files changed, 1241 insertions(+), 664 deletions(-) create mode 100644 src/components/notes/NoteCreationForm/index.tsx create mode 100644 src/components/notes/NoteCreationForm/styles.module.scss create mode 100644 src/constants/sidebar/components.ts create mode 100644 src/containers/auth/SuperPowersToggle/index.tsx rename src/{components => containers}/boris/BorisSidebar/index.tsx (65%) delete mode 100644 src/containers/dialogs/ProfileDialog/index.tsx delete mode 100644 src/containers/dialogs/ProfileDialog/styles.module.scss create mode 100644 src/containers/profile/ProfileToggles/index.tsx create mode 100644 src/hooks/auth/useSuperPowers.ts create mode 100644 src/hooks/dom/useConfirmation.ts delete mode 100644 src/hooks/notes/useGetNotes.ts create mode 100644 src/hooks/notes/useNotes.ts create mode 100644 src/types/sidebar/index.ts create mode 100644 src/utils/providers/NoteProvider.tsx delete mode 100644 src/utils/user.ts diff --git a/.env.local b/.env.local index 91636404..8e3e9501 100644 --- a/.env.local +++ b/.env.local @@ -1,6 +1,6 @@ NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/ NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/ -#NEXT_PUBLIC_API_HOST=http://localhost:8888/ -#NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/ -#NEXT_PUBLIC_API_HOST=https://pig.vault48.org/ -#NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/ +# NEXT_PUBLIC_API_HOST=http://localhost:8888/ +# NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/ +# NEXT_PUBLIC_API_HOST=https://pig.vault48.org/ +# NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/ diff --git a/.gitignore b/.gitignore index af8eab43..64b4f398 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ /.next /.vscode /.history -.DS_Store \ No newline at end of file +.DS_Store +/tsconfig.tsbuildinfo +.env.local \ No newline at end of file diff --git a/package.json b/package.json index f1a0721a..bf70f4f4 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "husky": "^7.0.4", "lint-staged": "^12.1.6", "next-transpile-modules": "^9.0.0", - "prettier": "^1.18.2" + "prettier": "^2.7.1" }, "lint-staged": { "./**/*.{js,jsx,ts,tsx}": [ diff --git a/src/api/notes/index.ts b/src/api/notes/index.ts index 21206e45..898008f8 100644 --- a/src/api/notes/index.ts +++ b/src/api/notes/index.ts @@ -1,8 +1,31 @@ -import { ApiGetNotesRequest, ApiGetNotesResponse } from '~/api/notes/types'; -import { URLS } from '~/constants/urls'; -import { api, cleanResult } from '~/utils/api'; +import { + ApiGetNotesRequest as ApiListNotesRequest, + ApiGetNotesResponse, + ApiCreateNoteRequest, + ApiUpdateNoteResponse, + ApiUpdateNoteRequest, +} from "~/api/notes/types"; +import { URLS } from "~/constants/urls"; +import { api, cleanResult } from "~/utils/api"; -export const apiGetNotes = ({ limit, offset, search }: ApiGetNotesRequest) => +export const apiListNotes = ({ limit, offset, search }: ApiListNotesRequest) => api .get(URLS.NOTES, { params: { limit, offset, search } }) .then(cleanResult); + +export const apiCreateNote = ({ text }: ApiCreateNoteRequest) => + api + .post(URLS.NOTES, { + text, + }) + .then(cleanResult); + +export const apiDeleteNote = (id: number) => + api.delete(URLS.NOTE(id)).then(cleanResult); + +export const apiUpdateNote = ({ id, text }: ApiUpdateNoteRequest) => + api + .put(URLS.NOTE(id), { + content: text, + }) + .then(cleanResult); diff --git a/src/api/notes/types.ts b/src/api/notes/types.ts index 921b686e..bc94d315 100644 --- a/src/api/notes/types.ts +++ b/src/api/notes/types.ts @@ -1,4 +1,4 @@ -import { Note } from '~/types/notes'; +import { Note } from "~/types/notes"; export interface ApiGetNotesRequest { limit: number; @@ -10,3 +10,14 @@ export interface ApiGetNotesResponse { list: Note[]; totalCount: number; } + +export interface ApiCreateNoteRequest { + text: string; +} + +export interface ApiUpdateNoteResponse extends Note {} + +export interface ApiUpdateNoteRequest { + id: number; + text: string; +} diff --git a/src/components/auth/login/LoginScene/index.tsx b/src/components/auth/login/LoginScene/index.tsx index 49fe29da..f03417af 100644 --- a/src/components/auth/login/LoginScene/index.tsx +++ b/src/components/auth/login/LoginScene/index.tsx @@ -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 = memo(() => { const containerRef = useRef(null); const [loaded, setLoaded] = useState(false); const imageRefs = useRef>([]); - const { isMobile } = useWindowSize(); + const { isTablet } = useWindowSize(); const domRect = useRef(); const onMouseMove = useCallback( @@ -84,11 +84,11 @@ const LoginScene: FC = 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 = memo(() => { > - + - + diff --git a/src/components/boris/Superpower/index.tsx b/src/components/boris/Superpower/index.tsx index 89348516..1ee45c4e 100644 --- a/src/components/boris/Superpower/index.tsx +++ b/src/components/boris/Superpower/index.tsx @@ -1,15 +1,17 @@ import React, { FC } from 'react'; +import { observer } from 'mobx-react-lite'; + import { useAuth } from '~/hooks/auth/useAuth'; interface IProps {} -const Superpower: FC = ({ children }) => { +const Superpower: FC = observer(({ children }) => { const { isTester } = useAuth(); if (!isTester) return null; return <>{children}; -}; +}); export { Superpower }; diff --git a/src/components/containers/Markdown/index.tsx b/src/components/containers/Markdown/index.tsx index 622aa068..3d9e349e 100644 --- a/src/components/containers/Markdown/index.tsx +++ b/src/components/containers/Markdown/index.tsx @@ -1,13 +1,21 @@ -import React, { DetailedHTMLProps, FC, HTMLAttributes } from 'react'; +import React, { DetailedHTMLProps, VFC, HTMLAttributes } from 'react'; import classNames from 'classnames'; import styles from '~/styles/common/markdown.module.scss'; +import { formatText } from '~/utils/dom'; -interface IProps extends DetailedHTMLProps, HTMLDivElement> {} +interface IProps + extends DetailedHTMLProps, HTMLDivElement> { + children?: string; +} -const Markdown: FC = ({ className, ...props }) => ( -
+const Markdown: VFC = ({ className, children = '', ...props }) => ( +
); export { Markdown }; diff --git a/src/components/containers/Zone/index.tsx b/src/components/containers/Zone/index.tsx index e26a63c8..86c13f71 100644 --- a/src/components/containers/Zone/index.tsx +++ b/src/components/containers/Zone/index.tsx @@ -1,22 +1,26 @@ -import React, { FC } from "react"; +import React, { FC } from 'react'; -import classNames from "classnames"; +import classNames from 'classnames'; -import styles from "./styles.module.scss"; +import styles from './styles.module.scss'; interface ZoneProps { title?: string; className?: string; - color?: "danger" | "normal"; + color?: 'danger' | 'normal'; } const Zone: FC = ({ title, className, children, - color = "normal", + color = 'normal', }) => ( -
+
{!!title && (
{title} diff --git a/src/components/containers/Zone/styles.module.scss b/src/components/containers/Zone/styles.module.scss index 4767c09a..707673b0 100644 --- a/src/components/containers/Zone/styles.module.scss +++ b/src/components/containers/Zone/styles.module.scss @@ -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; + } } diff --git a/src/components/editors/EditorButtons/index.tsx b/src/components/editors/EditorButtons/index.tsx index 1b11ddba..56b04a40 100644 --- a/src/components/editors/EditorButtons/index.tsx +++ b/src/components/editors/EditorButtons/index.tsx @@ -11,7 +11,7 @@ import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik'; const EditorButtons: FC = () => { const { values, handleChange, isSubmitting } = useNodeFormContext(); - const { isMobile } = useWindowSize(); + const { isTablet } = useWindowSize(); return ( @@ -23,14 +23,14 @@ const EditorButtons: FC = () => { title="Название" value={values.title} handler={handleChange('title')} - autoFocus={!isMobile} + autoFocus={!isTablet} maxLength={256} disabled={isSubmitting} />
- } +
- Профиль - Настройки - Выдох - + {(!photo || !photo.id) && } +
-
+ ); }; diff --git a/src/components/node/NodeAuthorBlock/index.tsx b/src/components/node/NodeAuthorBlock/index.tsx index 75015025..94f35103 100644 --- a/src/components/node/NodeAuthorBlock/index.tsx +++ b/src/components/node/NodeAuthorBlock/index.tsx @@ -3,7 +3,6 @@ import React, { FC, useCallback } from 'react'; import { Avatar } from '~/components/common/Avatar'; import { useUserDescription } from '~/hooks/auth/useUserDescription'; import { INodeUser } from '~/types'; -import { openUserProfile } from '~/utils/user'; import styles from './styles.module.scss'; @@ -12,8 +11,6 @@ interface Props { } const NodeAuthorBlock: FC = ({ user }) => { - const onOpenProfile = useCallback(() => openUserProfile(user?.username), [user]); - const description = useUserDescription(user); if (!user) { @@ -23,7 +20,7 @@ const NodeAuthorBlock: FC = ({ user }) => { const { fullname, username, photo } = user; return ( -
+
diff --git a/src/components/node/NodeEditMenu/index.tsx b/src/components/node/NodeEditMenu/index.tsx index ca500616..c7edb676 100644 --- a/src/components/node/NodeEditMenu/index.tsx +++ b/src/components/node/NodeEditMenu/index.tsx @@ -30,16 +30,19 @@ const NodeEditMenu: VFC = ({ onLock, onEdit, }) => { - const { isMobile } = useWindowSize(); + const { isTablet } = useWindowSize(); - if (isMobile) { + if (isTablet) { return ( } className={className} > {canStar && ( - + {isHeroic ? 'Убрать с главной' : 'На главную'} )} @@ -48,7 +51,10 @@ const NodeEditMenu: VFC = ({ Редактировать - + {isLocked ? 'Восстановить' : 'Удалить'} diff --git a/src/components/notes/NoteCard/index.tsx b/src/components/notes/NoteCard/index.tsx index a87cf6aa..b2d54601 100644 --- a/src/components/notes/NoteCard/index.tsx +++ b/src/components/notes/NoteCard/index.tsx @@ -1,4 +1,4 @@ -import React, { VFC } from 'react'; +import React, { useCallback, useState, VFC } from 'react'; import { Card } from '~/components/containers/Card'; import { Markdown } from '~/components/containers/Markdown'; @@ -6,22 +6,56 @@ import { Padder } from '~/components/containers/Padder'; import { NoteMenu } from '~/components/notes/NoteMenu'; import { formatText, getPrettyDate } from '~/utils/dom'; +import { NoteCreationForm } from '../NoteCreationForm'; + import styles from './styles.module.scss'; interface NoteCardProps { content: string; + remove: () => Promise; + update: (text: string, callback?: () => void) => Promise; createdAt: string; } -const NoteCard: VFC = ({ content, createdAt }) => ( - - - - - +const NoteCard: VFC = ({ + content, + createdAt, + remove, + update, +}) => { + const [editing, setEditing] = useState(false); - {getPrettyDate(createdAt)} - -); + const toggleEditing = useCallback(() => setEditing(v => !v), []); + const onUpdate = useCallback( + (text: string, callback?: () => void) => + update(text, () => { + setEditing(false); + callback?.(); + }), + [], + ); + + return ( + + {editing ? ( + + ) : ( + <> + + + + {formatText(content)} + + + {getPrettyDate(createdAt)} + + )} + + ); +}; export { NoteCard }; diff --git a/src/components/notes/NoteCard/styles.module.scss b/src/components/notes/NoteCard/styles.module.scss index 2a7e7c8c..c909430b 100644 --- a/src/components/notes/NoteCard/styles.module.scss +++ b/src/components/notes/NoteCard/styles.module.scss @@ -6,10 +6,6 @@ word-break: break-word; padding: 0; position: relative; - - & > * { - @include row_shadow; - } } .footer { diff --git a/src/components/notes/NoteCreationForm/index.tsx b/src/components/notes/NoteCreationForm/index.tsx new file mode 100644 index 00000000..e07935b6 --- /dev/null +++ b/src/components/notes/NoteCreationForm/index.tsx @@ -0,0 +1,99 @@ +import { FC, useCallback } from 'react'; + +import { FormikConfig, useFormik } from 'formik'; +import { Asserts, object, string } from 'yup'; + +import { Card } from '~/components/containers/Card'; +import { Filler } from '~/components/containers/Filler'; +import { Group } from '~/components/containers/Group'; +import { Button } from '~/components/input/Button'; +import { Textarea } from '~/components/input/Textarea'; +import { useRandomPhrase } from '~/constants/phrases'; +import { getErrorMessage } from '~/utils/errors/getErrorMessage'; +import { showErrorToast } from '~/utils/errors/showToast'; + +import styles from './styles.module.scss'; + +interface NoteCreationFormProps { + text?: string; + onSubmit: (text: string, callback: () => void) => void; + onCancel: () => void; +} + +const validationSchema = object({ + text: string().required('Напишите что-нибудь'), +}); + +type Values = Asserts; + +const NoteCreationForm: FC = ({ + text = '', + onSubmit, + onCancel, +}) => { + const placeholder = useRandomPhrase('SIMPLE'); + + const submit = useCallback['onSubmit']>( + async (values, { resetForm, setSubmitting, setErrors }) => { + try { + await onSubmit(values.text, () => resetForm()); + } catch (error) { + const message = getErrorMessage(error); + if (message) { + setErrors({ text: message }); + return; + } + + showErrorToast(error); + } finally { + setSubmitting(false); + } + }, + [onSubmit], + ); + + const { + values, + errors, + handleChange, + handleSubmit, + touched, + handleBlur, + isSubmitting, + } = useFormik({ + initialValues: { text }, + validationSchema, + onSubmit: submit, + }); + + return ( +
+ +
+