mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
add comments for guests
This commit is contained in:
parent
cbf7b1f616
commit
8abf6177b5
16 changed files with 278 additions and 194 deletions
|
@ -21,7 +21,7 @@ type IProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
isSame?: boolean;
|
isSame?: boolean;
|
||||||
canEdit?: boolean;
|
canEdit?: boolean;
|
||||||
highlighted?: boolean;
|
highlighted?: boolean;
|
||||||
saveComment: (data: IComment) => Promise<unknown>;
|
saveComment: (data: IComment) => Promise<IComment | undefined>;
|
||||||
onDelete: (id: IComment['id'], isLocked: boolean) => void;
|
onDelete: (id: IComment['id'], isLocked: boolean) => void;
|
||||||
onShowImageModal: (images: IFile[], index: number) => void;
|
onShowImageModal: (images: IFile[], index: number) => void;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,11 +7,15 @@ import { IUser } from '~/types/auth';
|
||||||
import { path } from '~/utils/ramda';
|
import { path } from '~/utils/ramda';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: IUser;
|
user?: IUser;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommentAvatar: FC<Props> = ({ user, className }) => {
|
const CommentAvatar: FC<Props> = ({ user, className }) => {
|
||||||
|
if (!user) {
|
||||||
|
return <Avatar className={className} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuButton
|
<MenuButton
|
||||||
position="auto"
|
position="auto"
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { IComment, IFile } from '~/types';
|
||||||
import { formatCommentText, getPrettyDate, getURL } from '~/utils/dom';
|
import { formatCommentText, getPrettyDate, getURL } from '~/utils/dom';
|
||||||
import { append, assocPath, path, reduce } from '~/utils/ramda';
|
import { append, assocPath, path, reduce } from '~/utils/ramda';
|
||||||
|
|
||||||
|
import { CommentEditingForm } from '../CommentEditingForm';
|
||||||
import { CommentImageGrid } from '../CommentImageGrid';
|
import { CommentImageGrid } from '../CommentImageGrid';
|
||||||
import { CommentMenu } from '../CommentMenu';
|
import { CommentMenu } from '../CommentMenu';
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ interface IProps {
|
||||||
nodeId: number;
|
nodeId: number;
|
||||||
comment: IComment;
|
comment: IComment;
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
saveComment: (data: IComment) => Promise<unknown>;
|
saveComment: (data: IComment) => Promise<IComment | undefined>;
|
||||||
onDelete: (id: IComment['id'], isLocked: boolean) => void;
|
onDelete: (id: IComment['id'], isLocked: boolean) => void;
|
||||||
onShowImageModal: (images: IFile[], index: number) => void;
|
onShowImageModal: (images: IFile[], index: number) => void;
|
||||||
}
|
}
|
||||||
|
@ -98,7 +99,7 @@ const CommentContent: FC<IProps> = memo(
|
||||||
|
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
return (
|
return (
|
||||||
<CommentForm
|
<CommentEditingForm
|
||||||
saveComment={saveComment}
|
saveComment={saveComment}
|
||||||
nodeId={nodeId}
|
nodeId={nodeId}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
|
|
38
src/components/comment/CommentEditingForm/index.tsx
Normal file
38
src/components/comment/CommentEditingForm/index.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { FC } from 'react';
|
||||||
|
|
||||||
|
import { CommentForm } from '~/components/comment/CommentForm';
|
||||||
|
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||||
|
import { UploadSubject, UploadTarget } from '~/constants/uploads';
|
||||||
|
import { useUploader } from '~/hooks/data/useUploader';
|
||||||
|
import { IComment, INode } from '~/types';
|
||||||
|
import { UploaderContextProvider } from '~/utils/context/UploaderContextProvider';
|
||||||
|
|
||||||
|
interface CommentEditingFormProps {
|
||||||
|
comment: IComment;
|
||||||
|
nodeId: INode['id'];
|
||||||
|
saveComment: (data: IComment) => Promise<IComment | undefined>;
|
||||||
|
onCancelEdit?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommentEditingForm: FC<CommentEditingFormProps> = ({
|
||||||
|
saveComment,
|
||||||
|
comment,
|
||||||
|
onCancelEdit,
|
||||||
|
}) => {
|
||||||
|
const uploader = useUploader(UploadSubject.Comment, UploadTarget.Comments);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UploadDropzone onUpload={uploader.uploadFiles}>
|
||||||
|
<UploaderContextProvider value={uploader}>
|
||||||
|
<CommentForm
|
||||||
|
saveComment={saveComment}
|
||||||
|
comment={comment}
|
||||||
|
onCancelEdit={onCancelEdit}
|
||||||
|
allowUploads
|
||||||
|
/>
|
||||||
|
</UploaderContextProvider>
|
||||||
|
</UploadDropzone>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { CommentEditingForm };
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { FC, useCallback, useState } from 'react';
|
import { FC, useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { FormikProvider } from 'formik';
|
import { FormikProvider } from 'formik';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
@ -12,31 +12,35 @@ import { Button } from '~/components/input/Button';
|
||||||
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||||
import { ERROR_LITERAL } from '~/constants/errors';
|
import { ERROR_LITERAL } from '~/constants/errors';
|
||||||
import { EMPTY_COMMENT } from '~/constants/node';
|
import { EMPTY_COMMENT } from '~/constants/node';
|
||||||
import { UploadSubject, UploadTarget } from '~/constants/uploads';
|
|
||||||
import { useCommentFormFormik } from '~/hooks/comments/useCommentFormFormik';
|
import { useCommentFormFormik } from '~/hooks/comments/useCommentFormFormik';
|
||||||
import { useUploader } from '~/hooks/data/useUploader';
|
|
||||||
import { useInputPasteUpload } from '~/hooks/dom/useInputPasteUpload';
|
import { useInputPasteUpload } from '~/hooks/dom/useInputPasteUpload';
|
||||||
import { IComment, INode } from '~/types';
|
import { IComment, INode } from '~/types';
|
||||||
import { UploaderContextProvider } from '~/utils/context/UploaderContextProvider';
|
import {
|
||||||
|
UploaderContextProvider,
|
||||||
|
useUploaderContext,
|
||||||
|
} from '~/utils/context/UploaderContextProvider';
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
comment?: IComment;
|
comment?: IComment;
|
||||||
nodeId: INode['id'];
|
allowUploads?: boolean;
|
||||||
saveComment: (data: IComment) => Promise<unknown>;
|
|
||||||
|
saveComment: (data: IComment) => Promise<IComment | undefined>;
|
||||||
onCancelEdit?: () => void;
|
onCancelEdit?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommentForm: FC<IProps> = observer(({ comment, nodeId, saveComment, onCancelEdit }) => {
|
const CommentForm: FC<IProps> = observer(
|
||||||
|
({ comment, allowUploads, saveComment, onCancelEdit }) => {
|
||||||
const [textarea, setTextArea] = useState<HTMLTextAreaElement | null>(null);
|
const [textarea, setTextArea] = useState<HTMLTextAreaElement | null>(null);
|
||||||
const uploader = useUploader(UploadSubject.Comment, UploadTarget.Comments, comment?.files);
|
const uploader = useUploaderContext();
|
||||||
|
|
||||||
const formik = useCommentFormFormik(
|
const formik = useCommentFormFormik(
|
||||||
comment || EMPTY_COMMENT,
|
comment || EMPTY_COMMENT,
|
||||||
nodeId,
|
uploader.files,
|
||||||
uploader,
|
uploader.setFiles,
|
||||||
saveComment,
|
saveComment,
|
||||||
onCancelEdit
|
onCancelEdit,
|
||||||
);
|
);
|
||||||
const isLoading = formik.isSubmitting || uploader.isUploading;
|
const isLoading = formik.isSubmitting || uploader.isUploading;
|
||||||
const isEditing = !!comment?.id;
|
const isEditing = !!comment?.id;
|
||||||
|
@ -58,10 +62,8 @@ const CommentForm: FC<IProps> = observer(({ comment, nodeId, saveComment, onCanc
|
||||||
const onPaste = useInputPasteUpload(uploader.uploadFiles);
|
const onPaste = useInputPasteUpload(uploader.uploadFiles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UploadDropzone onUpload={uploader.uploadFiles}>
|
|
||||||
<form onSubmit={formik.handleSubmit} className={styles.wrap}>
|
<form onSubmit={formik.handleSubmit} className={styles.wrap}>
|
||||||
<FormikProvider value={formik}>
|
<FormikProvider value={formik}>
|
||||||
<UploaderContextProvider value={uploader}>
|
|
||||||
<div className={styles.input}>
|
<div className={styles.input}>
|
||||||
<LocalCommentFormTextarea onPaste={onPaste} ref={setTextArea} />
|
<LocalCommentFormTextarea onPaste={onPaste} ref={setTextArea} />
|
||||||
|
|
||||||
|
@ -72,12 +74,14 @@ const CommentForm: FC<IProps> = observer(({ comment, nodeId, saveComment, onCanc
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CommentFormAttaches />
|
{allowUploads && <CommentFormAttaches />}
|
||||||
|
|
||||||
<div className={styles.buttons}>
|
<div className={styles.buttons}>
|
||||||
|
{allowUploads && (
|
||||||
<div className={styles.button_column}>
|
<div className={styles.button_column}>
|
||||||
<CommentFormAttachButtons onUpload={uploader.uploadFiles} />
|
<CommentFormAttachButtons onUpload={uploader.uploadFiles} />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={styles.button_column}>
|
<div className={styles.button_column}>
|
||||||
{!!textarea && (
|
{!!textarea && (
|
||||||
|
@ -92,7 +96,12 @@ const CommentForm: FC<IProps> = observer(({ comment, nodeId, saveComment, onCanc
|
||||||
|
|
||||||
<div className={styles.button_column}>
|
<div className={styles.button_column}>
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
<Button size="small" color="link" type="button" onClick={onCancelEdit}>
|
<Button
|
||||||
|
size="small"
|
||||||
|
color="link"
|
||||||
|
type="button"
|
||||||
|
onClick={onCancelEdit}
|
||||||
|
>
|
||||||
Отмена
|
Отмена
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
@ -109,11 +118,10 @@ const CommentForm: FC<IProps> = observer(({ comment, nodeId, saveComment, onCanc
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UploaderContextProvider>
|
|
||||||
</FormikProvider>
|
</FormikProvider>
|
||||||
</form>
|
</form>
|
||||||
</UploadDropzone>
|
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export { CommentForm };
|
export { CommentForm };
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { DivProps } from '~/utils/types';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
type IProps = DivProps & {
|
type IProps = DivProps & {
|
||||||
user: IUser;
|
user?: IUser;
|
||||||
isEmpty?: boolean;
|
isEmpty?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
isForm?: boolean;
|
isForm?: boolean;
|
||||||
|
@ -36,7 +36,10 @@ const CommentWrapper: FC<IProps> = ({
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className={styles.thumb}>
|
<div className={styles.thumb}>
|
||||||
<CommentAvatar user={user} className={styles.thumb_image} />
|
<CommentAvatar
|
||||||
|
user={user}
|
||||||
|
className={classNames(styles.thumb_image, { [styles.pointer]: user })}
|
||||||
|
/>
|
||||||
<div className={styles.thumb_user}>~{path(['username'], user)}</div>
|
<div className={styles.thumb_user}>~{path(['username'], user)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ div.thumb_image {
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
flex: 0 0 $comment_height;
|
flex: 0 0 $comment_height;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
cursor: pointer;
|
cursor: default;
|
||||||
|
|
||||||
@include tablet {
|
@include tablet {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
@ -105,6 +105,10 @@ div.thumb_image {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.thumb_user {
|
.thumb_user {
|
||||||
display: none;
|
display: none;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import React, { FC } from 'react';
|
|
||||||
|
|
||||||
import { CommentForm } from '~/components/comment/CommentForm';
|
|
||||||
import { CommentWrapper } from '~/components/containers/CommentWrapper';
|
|
||||||
import { IComment } from '~/types';
|
|
||||||
import { IUser } from '~/types/auth';
|
|
||||||
|
|
||||||
export interface NodeCommentFormProps {
|
|
||||||
user: IUser;
|
|
||||||
nodeId?: number;
|
|
||||||
saveComment: (comment: IComment) => Promise<unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NodeCommentForm: FC<NodeCommentFormProps> = ({ user, nodeId, saveComment }) => {
|
|
||||||
return (
|
|
||||||
<CommentWrapper user={user} isForm>
|
|
||||||
<CommentForm nodeId={nodeId} saveComment={saveComment} />
|
|
||||||
</CommentWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { NodeCommentForm };
|
|
|
@ -1,8 +0,0 @@
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
|
|
||||||
import type { NodeCommentFormProps } from './index';
|
|
||||||
|
|
||||||
export const NodeCommentFormSSR = dynamic<NodeCommentFormProps>(
|
|
||||||
() => import('./index').then(it => it.NodeCommentForm),
|
|
||||||
{ ssr: false }
|
|
||||||
);
|
|
|
@ -1,34 +1,20 @@
|
||||||
import React, { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from '~/components/containers/Group';
|
||||||
import { Footer } from '~/components/main/Footer';
|
import { Footer } from '~/components/main/Footer';
|
||||||
import { NodeCommentFormSSR } from '~/components/node/NodeCommentForm/ssr';
|
|
||||||
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
||||||
import { isSSR } from '~/constants/ssr';
|
import { NodeCommentFormSSR } from '~/containers/node/NodeCommentForm/ssr';
|
||||||
import { NodeComments } from '~/containers/node/NodeComments';
|
import { NodeComments } from '~/containers/node/NodeComments';
|
||||||
import { useAuth } from '~/hooks/auth/useAuth';
|
|
||||||
import { useCommentContext } from '~/utils/context/CommentContextProvider';
|
import { useCommentContext } from '~/utils/context/CommentContextProvider';
|
||||||
import { useNodeContext } from '~/utils/context/NodeContextProvider';
|
|
||||||
import { useUserContext } from '~/utils/context/UserContextProvider';
|
|
||||||
|
|
||||||
interface IProps {}
|
interface Props {}
|
||||||
|
|
||||||
const BorisComments: FC<IProps> = () => {
|
|
||||||
const user = useUserContext();
|
|
||||||
const { isUser } = useAuth();
|
|
||||||
|
|
||||||
|
const BorisComments: FC<Props> = () => {
|
||||||
const { isLoading, comments, onSaveComment } = useCommentContext();
|
const { isLoading, comments, onSaveComment } = useCommentContext();
|
||||||
const { node } = useNodeContext();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
{(isUser || isSSR) && (
|
<NodeCommentFormSSR saveComment={onSaveComment} />
|
||||||
<NodeCommentFormSSR
|
|
||||||
user={user}
|
|
||||||
nodeId={node.id}
|
|
||||||
saveComment={onSaveComment}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isLoading || !comments?.length ? (
|
{isLoading || !comments?.length ? (
|
||||||
<NodeNoComments loading count={7} />
|
<NodeNoComments loading count={7} />
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
import React, { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
|
||||||
import { Card } from '~/components/containers/Card';
|
|
||||||
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 { Sticky } from '~/components/containers/Sticky';
|
import { Sticky } from '~/components/containers/Sticky';
|
||||||
import { NodeAuthorBlock } from '~/components/node/NodeAuthorBlock';
|
import { NodeAuthorBlock } from '~/components/node/NodeAuthorBlock';
|
||||||
import { NodeCommentFormSSR } from '~/components/node/NodeCommentForm/ssr';
|
|
||||||
import { NodeDeletedBadge } from '~/components/node/NodeDeletedBadge';
|
import { NodeDeletedBadge } from '~/components/node/NodeDeletedBadge';
|
||||||
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
||||||
import { NodeRelatedBlock } from '~/components/node/NodeRelatedBlock';
|
import { NodeRelatedBlock } from '~/components/node/NodeRelatedBlock';
|
||||||
import { NodeTagsBlock } from '~/components/node/NodeTagsBlock';
|
import { NodeTagsBlock } from '~/components/node/NodeTagsBlock';
|
||||||
import { NodeBacklinks } from '~/containers/node/NodeBacklinks';
|
import { NodeBacklinks } from '~/containers/node/NodeBacklinks';
|
||||||
|
import { NodeCommentFormSSR } from '~/containers/node/NodeCommentForm/ssr';
|
||||||
import { NodeComments } from '~/containers/node/NodeComments';
|
import { NodeComments } from '~/containers/node/NodeComments';
|
||||||
import { useNodeBlocks } from '~/hooks/node/useNodeBlocks';
|
import { useNodeBlocks } from '~/hooks/node/useNodeBlocks';
|
||||||
import { useCommentContext } from '~/utils/context/CommentContextProvider';
|
import { useCommentContext } from '~/utils/context/CommentContextProvider';
|
||||||
import { useNodeContext } from '~/utils/context/NodeContextProvider';
|
import { useNodeContext } from '~/utils/context/NodeContextProvider';
|
||||||
import { useNodeRelatedContext } from '~/utils/context/NodeRelatedContextProvider';
|
import { useNodeRelatedContext } from '~/utils/context/NodeRelatedContextProvider';
|
||||||
import { useUserContext } from '~/utils/context/UserContextProvider';
|
|
||||||
import { useAuthProvider } from '~/utils/providers/AuthProvider';
|
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
|
@ -27,7 +24,6 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeBottomBlock: FC<IProps> = ({ commentsOrder }) => {
|
const NodeBottomBlock: FC<IProps> = ({ commentsOrder }) => {
|
||||||
const user = useUserContext();
|
|
||||||
const { node, isLoading, backlinks } = useNodeContext();
|
const { node, isLoading, backlinks } = useNodeContext();
|
||||||
const {
|
const {
|
||||||
comments,
|
comments,
|
||||||
|
@ -36,7 +32,6 @@ const NodeBottomBlock: FC<IProps> = ({ commentsOrder }) => {
|
||||||
} = useCommentContext();
|
} = useCommentContext();
|
||||||
const { related, isLoading: isLoadingRelated } = useNodeRelatedContext();
|
const { related, isLoading: isLoadingRelated } = useNodeRelatedContext();
|
||||||
const { inline } = useNodeBlocks(node, isLoading);
|
const { inline } = useNodeBlocks(node, isLoading);
|
||||||
const { isUser } = useAuthProvider();
|
|
||||||
|
|
||||||
if (node.deleted_at) {
|
if (node.deleted_at) {
|
||||||
return <NodeDeletedBadge />;
|
return <NodeDeletedBadge />;
|
||||||
|
@ -59,13 +54,7 @@ const NodeBottomBlock: FC<IProps> = ({ commentsOrder }) => {
|
||||||
)}
|
)}
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
{isUser && !isLoading && (
|
<NodeCommentFormSSR saveComment={onSaveComment} />
|
||||||
<NodeCommentFormSSR
|
|
||||||
nodeId={node.id}
|
|
||||||
saveComment={onSaveComment}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className={styles.subheader}>
|
<div className={styles.subheader}>
|
||||||
<Filler className={styles.backlinks}>
|
<Filler className={styles.backlinks}>
|
||||||
|
|
47
src/containers/node/NodeCommentForm/index.tsx
Normal file
47
src/containers/node/NodeCommentForm/index.tsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { FC, useCallback } from 'react';
|
||||||
|
|
||||||
|
import { CommentForm } from '~/components/comment/CommentForm';
|
||||||
|
import { CommentWrapper } from '~/components/containers/CommentWrapper';
|
||||||
|
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||||
|
import { EMPTY_USER } from '~/constants/auth';
|
||||||
|
import { Dialog } from '~/constants/modal';
|
||||||
|
import { UploadSubject, UploadTarget } from '~/constants/uploads';
|
||||||
|
import { useAuth } from '~/hooks/auth/useAuth';
|
||||||
|
import { useUploader } from '~/hooks/data/useUploader';
|
||||||
|
import { useShowModal } from '~/hooks/modal/useShowModal';
|
||||||
|
import { IComment } from '~/types';
|
||||||
|
import { UploaderContextProvider } from '~/utils/context/UploaderContextProvider';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
saveComment: (comment: IComment) => Promise<IComment | undefined>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NodeCommentForm: FC<Props> = ({ saveComment }) => {
|
||||||
|
const { user, isUser } = useAuth();
|
||||||
|
const showLoginDialog = useShowModal(Dialog.Login);
|
||||||
|
|
||||||
|
const uploader = useUploader(UploadSubject.Comment, UploadTarget.Comments);
|
||||||
|
const onCommentSave = useCallback(
|
||||||
|
async (comment: IComment) => {
|
||||||
|
if (!isUser) {
|
||||||
|
showLoginDialog({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return saveComment(comment);
|
||||||
|
},
|
||||||
|
[isUser, showLoginDialog, saveComment],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UploadDropzone onUpload={uploader.uploadFiles}>
|
||||||
|
<UploaderContextProvider value={uploader}>
|
||||||
|
<CommentWrapper user={isUser ? user : undefined} isForm>
|
||||||
|
<CommentForm saveComment={onCommentSave} allowUploads={isUser} />
|
||||||
|
</CommentWrapper>
|
||||||
|
</UploaderContextProvider>
|
||||||
|
</UploadDropzone>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { NodeCommentForm };
|
8
src/containers/node/NodeCommentForm/ssr.ts
Normal file
8
src/containers/node/NodeCommentForm/ssr.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
import type { Props } from './index';
|
||||||
|
|
||||||
|
export const NodeCommentFormSSR = dynamic<Props>(
|
||||||
|
() => import('./index').then((it) => it.NodeCommentForm),
|
||||||
|
{ ssr: false },
|
||||||
|
);
|
|
@ -3,23 +3,22 @@ import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { FormikHelpers, useFormik, useFormikContext } from 'formik';
|
import { FormikHelpers, useFormik, useFormikContext } from 'formik';
|
||||||
import { array, object, string } from 'yup';
|
import { array, object, string } from 'yup';
|
||||||
|
|
||||||
import { IComment, INode } from '~/types';
|
import { IComment, IFile } from '~/types';
|
||||||
import { Uploader } from '~/utils/context/UploaderContextProvider';
|
import { getErrorMessage } from '~/utils/errors/getErrorMessage';
|
||||||
import { showErrorToast } from '~/utils/errors/showToast';
|
import { showErrorToast } from '~/utils/errors/showToast';
|
||||||
import { hasPath, path } from '~/utils/ramda';
|
|
||||||
|
|
||||||
const validationSchema = object().shape({
|
const validationSchema = object().shape({
|
||||||
text: string(),
|
text: string(),
|
||||||
files: array(),
|
files: array(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSuccess = ({ resetForm, setSubmitting, setErrors }: FormikHelpers<IComment>) => (
|
const onSuccess =
|
||||||
error?: unknown
|
({ resetForm, setSubmitting, setErrors }: FormikHelpers<IComment>) =>
|
||||||
) => {
|
(error?: unknown) => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
const message = getErrorMessage(error);
|
||||||
|
|
||||||
if (hasPath(['response', 'data', 'error'], error)) {
|
if (message) {
|
||||||
const message = path(['response', 'data', 'error'], error) as string;
|
|
||||||
setErrors({ text: message });
|
setErrors({ text: message });
|
||||||
showErrorToast(error);
|
showErrorToast(error);
|
||||||
return;
|
return;
|
||||||
|
@ -28,34 +27,38 @@ const onSuccess = ({ resetForm, setSubmitting, setErrors }: FormikHelpers<IComme
|
||||||
if (resetForm) {
|
if (resetForm) {
|
||||||
resetForm();
|
resetForm();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCommentFormFormik = (
|
export const useCommentFormFormik = (
|
||||||
values: IComment,
|
comment: IComment,
|
||||||
nodeId: INode['id'],
|
files: IFile[],
|
||||||
uploader: Uploader,
|
setFiles: (file: IFile[]) => void,
|
||||||
sendData: (data: IComment) => Promise<unknown>,
|
sendData: (data: IComment) => Promise<IComment | undefined>,
|
||||||
stopEditing?: () => void
|
stopEditing?: () => void,
|
||||||
) => {
|
) => {
|
||||||
const { current: initialValues } = useRef(values);
|
const { current: initialValues } = useRef(comment);
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
async (values: IComment, helpers: FormikHelpers<IComment>) => {
|
async (values: IComment, helpers: FormikHelpers<IComment>) => {
|
||||||
try {
|
try {
|
||||||
helpers.setSubmitting(true);
|
helpers.setSubmitting(true);
|
||||||
await sendData({ ...values, files: uploader.files });
|
|
||||||
|
const comment = await sendData({ ...values, files });
|
||||||
|
|
||||||
|
if (comment) {
|
||||||
onSuccess(helpers)();
|
onSuccess(helpers)();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
onSuccess(helpers)(error);
|
onSuccess(helpers)(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[sendData, uploader.files]
|
[sendData, files],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onReset = useCallback(() => {
|
const onReset = useCallback(() => {
|
||||||
uploader.setFiles([]);
|
setFiles([]);
|
||||||
if (stopEditing) stopEditing();
|
if (stopEditing) stopEditing();
|
||||||
}, [stopEditing, uploader]);
|
}, [stopEditing, setFiles]);
|
||||||
|
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
initialValues,
|
initialValues,
|
||||||
|
|
|
@ -26,17 +26,19 @@ export const useNodeComments = (nodeId: number, fallbackData?: IComment[]) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await mutate(
|
await mutate(
|
||||||
prev =>
|
(prev) =>
|
||||||
prev?.map(list =>
|
prev?.map((list) =>
|
||||||
list.map(comment => (comment.id === id ? { ...comment, deleted_at } : comment))
|
list.map((comment) =>
|
||||||
|
comment.id === id ? { ...comment, deleted_at } : comment,
|
||||||
),
|
),
|
||||||
false
|
),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast(error);
|
showErrorToast(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[data, mutate, nodeId]
|
[data, mutate, nodeId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onEdit = useCallback(
|
const onEdit = useCallback(
|
||||||
|
@ -50,22 +52,37 @@ export const useNodeComments = (nodeId: number, fallbackData?: IComment[]) => {
|
||||||
// Comment was created
|
// Comment was created
|
||||||
if (!comment.id) {
|
if (!comment.id) {
|
||||||
await mutate(
|
await mutate(
|
||||||
data.map((list, index) => (index === 0 ? [result.comment, ...list] : list)),
|
data.map((list, index) =>
|
||||||
false
|
index === 0 ? [result.comment, ...list] : list,
|
||||||
|
),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
|
return result.comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
await mutate(
|
await mutate(
|
||||||
prev =>
|
(prev) =>
|
||||||
prev?.map(list =>
|
prev?.map((list) =>
|
||||||
list.map(it => (it.id === result.comment.id ? { ...it, ...result.comment } : it))
|
list.map((it) =>
|
||||||
|
it.id === result.comment.id ? { ...it, ...result.comment } : it,
|
||||||
),
|
),
|
||||||
false
|
),
|
||||||
);
|
false,
|
||||||
},
|
|
||||||
[data, mutate, nodeId]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return { onLoadMoreComments, onDelete, comments, hasMore, isLoading, onEdit, isLoadingMore };
|
return result.comment;
|
||||||
|
},
|
||||||
|
[data, mutate, nodeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
onLoadMoreComments,
|
||||||
|
onDelete,
|
||||||
|
comments,
|
||||||
|
hasMore,
|
||||||
|
isLoading,
|
||||||
|
onEdit,
|
||||||
|
isLoadingMore,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,25 +10,31 @@ export interface CommentProviderProps {
|
||||||
isLoadingMore: boolean;
|
isLoadingMore: boolean;
|
||||||
onShowImageModal: (images: IFile[], index: number) => void;
|
onShowImageModal: (images: IFile[], index: number) => void;
|
||||||
onLoadMoreComments: () => void;
|
onLoadMoreComments: () => void;
|
||||||
onSaveComment: (comment: IComment) => Promise<unknown>;
|
onSaveComment: (comment: IComment) => Promise<IComment | undefined>;
|
||||||
onDeleteComment: (id: IComment['id'], isLocked: boolean) => void;
|
onDeleteComment: (id: IComment['id'], isLocked: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommentContext = createContext<CommentProviderProps>({
|
const CommentContext = createContext<CommentProviderProps>({
|
||||||
// user: EMPTY_USER,
|
|
||||||
comments: [],
|
comments: [],
|
||||||
hasMore: false,
|
hasMore: false,
|
||||||
lastSeenCurrent: null,
|
lastSeenCurrent: null,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isLoadingMore: false,
|
isLoadingMore: false,
|
||||||
onSaveComment: async () => {},
|
onSaveComment: async () => undefined,
|
||||||
onShowImageModal: () => {},
|
onShowImageModal: () => {},
|
||||||
onLoadMoreComments: () => {},
|
onLoadMoreComments: () => {},
|
||||||
onDeleteComment: () => {},
|
onDeleteComment: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CommentContextProvider: FC<CommentProviderProps> = ({ children, ...contextValue }) => {
|
export const CommentContextProvider: FC<CommentProviderProps> = ({
|
||||||
return <CommentContext.Provider value={contextValue}>{children}</CommentContext.Provider>;
|
children,
|
||||||
|
...contextValue
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<CommentContext.Provider value={contextValue}>
|
||||||
|
{children}
|
||||||
|
</CommentContext.Provider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCommentContext = () => useContext(CommentContext);
|
export const useCommentContext = () => useContext(CommentContext);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue