diff --git a/src/components/editors/AudioEditor/index.tsx b/src/components/editors/AudioEditor/index.tsx index 81c92931..335f651c 100644 --- a/src/components/editors/AudioEditor/index.tsx +++ b/src/components/editors/AudioEditor/index.tsx @@ -10,7 +10,7 @@ import styles from './styles.module.scss'; import { NodeEditorProps } from '~/redux/node/types'; import { useNodeImages } from '~/utils/hooks/node/useNodeImages'; import { useNodeAudios } from '~/utils/hooks/node/useNodeAudios'; -import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik'; +import { useNodeFormContext } from '~/utils/hooks/node/useNodeFormFormik'; import { useFileUploaderContext } from '~/utils/hooks/useFileUploader'; import { UploadDropzone } from '~/components/upload/UploadDropzone'; diff --git a/src/components/editors/EditorActionsPanel/index.tsx b/src/components/editors/EditorActionsPanel/index.tsx index 7f550f92..4a1f9164 100644 --- a/src/components/editors/EditorActionsPanel/index.tsx +++ b/src/components/editors/EditorActionsPanel/index.tsx @@ -1,9 +1,8 @@ import React, { FC, createElement } from 'react'; import styles from './styles.module.scss'; -import { INode } from '~/redux/types'; import { NODE_PANEL_COMPONENTS } from '~/redux/node/constants'; import { has } from 'ramda'; -import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik'; +import { useNodeFormContext } from '~/utils/hooks/node/useNodeFormFormik'; const EditorActionsPanel: FC = () => { const { values } = useNodeFormContext(); diff --git a/src/components/editors/EditorButtons/index.tsx b/src/components/editors/EditorButtons/index.tsx index 728093be..996dd2ba 100644 --- a/src/components/editors/EditorButtons/index.tsx +++ b/src/components/editors/EditorButtons/index.tsx @@ -4,7 +4,7 @@ import { Group } from '~/components/containers/Group'; import { InputText } from '~/components/input/InputText'; import { Button } from '~/components/input/Button'; import { Padder } from '~/components/containers/Padder'; -import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik'; +import { useNodeFormContext } from '~/utils/hooks/node/useNodeFormFormik'; const EditorButtons: FC = () => { const { values, handleChange, isSubmitting } = useNodeFormContext(); @@ -28,6 +28,7 @@ const EditorButtons: FC = () => { iconRight="check" color={values.is_promoted ? 'primary' : 'lab'} disabled={isSubmitting} + type="submit" /> diff --git a/src/components/editors/EditorPublicSwitch/index.tsx b/src/components/editors/EditorPublicSwitch/index.tsx index 4ba005ba..0022db25 100644 --- a/src/components/editors/EditorPublicSwitch/index.tsx +++ b/src/components/editors/EditorPublicSwitch/index.tsx @@ -3,7 +3,7 @@ import { IEditorComponentProps } from '~/redux/node/types'; import { Button } from '~/components/input/Button'; import { Icon } from '~/components/input/Icon'; import styles from './styles.module.scss'; -import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik'; +import { useNodeFormContext } from '~/utils/hooks/node/useNodeFormFormik'; interface IProps extends IEditorComponentProps {} diff --git a/src/components/editors/EditorUploadButton/index.tsx b/src/components/editors/EditorUploadButton/index.tsx index a5198d0a..a5b0849c 100644 --- a/src/components/editors/EditorUploadButton/index.tsx +++ b/src/components/editors/EditorUploadButton/index.tsx @@ -5,7 +5,7 @@ import { UPLOAD_TYPES } from '~/redux/uploads/constants'; import { IEditorComponentProps } from '~/redux/node/types'; import { useFileUploaderContext } from '~/utils/hooks/useFileUploader'; import { getFileType } from '~/utils/uploader'; -import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik'; +import { useNodeFormContext } from '~/utils/hooks/node/useNodeFormFormik'; import { Button } from '~/components/input/Button'; type IProps = IEditorComponentProps & { diff --git a/src/components/editors/EditorUploadCoverButton/index.tsx b/src/components/editors/EditorUploadCoverButton/index.tsx index 56cd55cd..f74ddfa1 100644 --- a/src/components/editors/EditorUploadCoverButton/index.tsx +++ b/src/components/editors/EditorUploadCoverButton/index.tsx @@ -12,7 +12,7 @@ import { Icon } from '~/components/input/Icon'; import { PRESETS } from '~/constants/urls'; import { IEditorComponentProps } from '~/redux/node/types'; import { useFileUploader, useFileUploaderContext } from '~/utils/hooks/useFileUploader'; -import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik'; +import { useNodeFormContext } from '~/utils/hooks/node/useNodeFormFormik'; import { getFileType } from '~/utils/uploader'; type IProps = IEditorComponentProps & {}; diff --git a/src/components/editors/TextEditor/index.tsx b/src/components/editors/TextEditor/index.tsx index cb2f9cbe..a6d7e217 100644 --- a/src/components/editors/TextEditor/index.tsx +++ b/src/components/editors/TextEditor/index.tsx @@ -4,7 +4,7 @@ import styles from './styles.module.scss'; import { Textarea } from '~/components/input/Textarea'; import { path } from 'ramda'; import { NodeEditorProps } from '~/redux/node/types'; -import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik'; +import { useNodeFormContext } from '~/utils/hooks/node/useNodeFormFormik'; type IProps = NodeEditorProps & {}; diff --git a/src/components/editors/VideoEditor/index.tsx b/src/components/editors/VideoEditor/index.tsx index 9fd60342..489046ac 100644 --- a/src/components/editors/VideoEditor/index.tsx +++ b/src/components/editors/VideoEditor/index.tsx @@ -6,7 +6,7 @@ import { InputText } from '~/components/input/InputText'; import classnames from 'classnames'; import { getYoutubeThumb } from '~/utils/dom'; import { NodeEditorProps } from '~/redux/node/types'; -import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik'; +import { useNodeFormContext } from '~/utils/hooks/node/useNodeFormFormik'; type IProps = NodeEditorProps & {}; diff --git a/src/components/node/NodeImageSwiperBlock/index.tsx b/src/components/node/NodeImageSwiperBlock/index.tsx index bcbefb9c..cc52d330 100644 --- a/src/components/node/NodeImageSwiperBlock/index.tsx +++ b/src/components/node/NodeImageSwiperBlock/index.tsx @@ -64,7 +64,6 @@ const NodeImageSwiperBlock: FC = ({ node }) => {
onOpenPhotoSwipe(0)} className={styles.image} /> diff --git a/src/containers/dialogs/EditorCreateDialog/index.tsx b/src/containers/dialogs/EditorCreateDialog/index.tsx index 6d8d1689..ab13143a 100644 --- a/src/containers/dialogs/EditorCreateDialog/index.tsx +++ b/src/containers/dialogs/EditorCreateDialog/index.tsx @@ -3,6 +3,10 @@ import { EMPTY_NODE, NODE_TYPES } from '~/redux/node/constants'; import { EditorDialog } from '~/containers/dialogs/EditorDialog'; import { useHistory, useRouteMatch } from 'react-router'; import { values } from 'ramda'; +import { INode } from '~/redux/types'; +import { apiPostNode } from '~/redux/node/api'; +import { useUpdateNode } from '~/utils/hooks/data/useUpdateNode'; +import { useCreateNode } from '~/utils/hooks/data/useCreateNode'; const EditorCreateDialog: FC = () => { const history = useHistory(); @@ -25,11 +29,21 @@ const EditorCreateDialog: FC = () => { const data = useRef({ ...EMPTY_NODE, type, is_promoted: !isInLab }); + const createNode = useCreateNode(); + + const onSubmit = useCallback( + async (node: INode) => { + await createNode(node); + goBack(); + }, + [goBack, createNode] + ); + if (!type || !isExist) { return null; } - return ; + return ; }; export { EditorCreateDialog }; diff --git a/src/containers/dialogs/EditorDialog/index.tsx b/src/containers/dialogs/EditorDialog/index.tsx index 7ba4854a..ebb67a0e 100644 --- a/src/containers/dialogs/EditorDialog/index.tsx +++ b/src/containers/dialogs/EditorDialog/index.tsx @@ -5,7 +5,7 @@ import { NODE_EDITORS } from '~/redux/node/constants'; import { BetterScrollDialog } from '../BetterScrollDialog'; import { CoverBackdrop } from '~/components/containers/CoverBackdrop'; import { prop } from 'ramda'; -import { useNodeFormFormik } from '~/utils/hooks/useNodeFormFormik'; +import { useNodeFormFormik } from '~/utils/hooks/node/useNodeFormFormik'; import { EditorButtons } from '~/components/editors/EditorButtons'; import { FileUploaderProvider, useFileUploader } from '~/utils/hooks/useFileUploader'; import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants'; @@ -15,16 +15,18 @@ import { ModalWrapper } from '~/components/dialogs/ModalWrapper'; import { useTranslatedError } from '~/utils/hooks/useTranslatedError'; import { useCloseOnEscape } from '~/utils/hooks'; import { EditorConfirmClose } from '~/components/editors/EditorConfirmClose'; +import { on } from 'cluster'; interface Props extends IDialogProps { node: INode; + onSubmit: (node: INode) => Promise; } -const EditorDialog: FC = ({ node, onRequestClose }) => { +const EditorDialog: FC = ({ node, onRequestClose, onSubmit }) => { const [isConfirmModalShown, setConfirmModalShown] = useState(false); const uploader = useFileUploader(UPLOAD_SUBJECTS.EDITOR, UPLOAD_TARGETS.NODES, node.files); - const formik = useNodeFormFormik(node, uploader, onRequestClose); + const formik = useNodeFormFormik(node, uploader, onRequestClose, onSubmit); const { values, handleSubmit, dirty, status } = formik; const component = useMemo(() => node.type && prop(node.type, NODE_EDITORS), [node.type]); @@ -71,6 +73,7 @@ const EditorDialog: FC = ({ node, onRequestClose }) => { {isConfirmModalShown && ( )} +
{createElement(component)}
diff --git a/src/containers/dialogs/EditorEditDialog/index.tsx b/src/containers/dialogs/EditorEditDialog/index.tsx index 6cd44ae6..61afe612 100644 --- a/src/containers/dialogs/EditorEditDialog/index.tsx +++ b/src/containers/dialogs/EditorEditDialog/index.tsx @@ -5,7 +5,8 @@ import { ModalWrapper } from '~/components/dialogs/ModalWrapper'; import { LoaderCircle } from '~/components/input/LoaderCircle'; import styles from './styles.module.scss'; import { useGetNode } from '~/utils/hooks/data/useGetNode'; -import { EMPTY_NODE } from '~/redux/node/constants'; +import { useUpdateNode } from '~/utils/hooks/data/useUpdateNode'; +import { INode } from '~/redux/types'; const EditorEditDialog: FC = () => { const history = useHistory(); @@ -24,6 +25,15 @@ const EditorEditDialog: FC = () => { }, [backUrl, history]); const { node, isLoading } = useGetNode(parseInt(id, 10)); + const updateNode = useUpdateNode(parseInt(id, 10)); + + const onSubmit = useCallback( + async (node: INode) => { + await updateNode(node); + goBack(); + }, + [updateNode, goBack] + ); if (isLoading || !node) { return ( @@ -35,7 +45,7 @@ const EditorEditDialog: FC = () => { ); } - return ; + return ; }; export { EditorEditDialog }; diff --git a/src/pages/node/[id].tsx b/src/pages/node/[id].tsx index 6d4067b5..2a22c0d9 100644 --- a/src/pages/node/[id].tsx +++ b/src/pages/node/[id].tsx @@ -12,6 +12,7 @@ import { CommentContextProvider } from '~/utils/context/CommentContextProvider'; import { TagsContextProvider } from '~/utils/context/TagsContextProvider'; import { useNodePermissions } from '~/utils/hooks/node/useNodePermissions'; import { NodeRelatedProvider } from '~/utils/providers/NodeRelatedProvider'; +import { useGetNode } from '~/utils/hooks/data/useGetNode'; type Props = RouteComponentProps<{ id: string }> & {}; @@ -20,14 +21,8 @@ const NodePage: FC = ({ params: { id }, }, }) => { - const { - node, - isLoading, - isLoadingComments, - comments, - commentsCount, - lastSeenCurrent, - } = useFullNode(id); + const { node, isLoading } = useGetNode(parseInt(id, 10)); + const { isLoadingComments, comments, commentsCount, lastSeenCurrent } = useFullNode(id); const onShowImageModal = useImageModal(); const { onLoadMoreComments, onDelete: onDeleteComment } = useNodeComments(parseInt(id, 10)); @@ -39,6 +34,11 @@ const NodePage: FC = ({ useScrollToTop([id, isLoadingComments]); + if (!node) { + // TODO: do something here + return null; + } + return ( diff --git a/src/redux/node/actions.ts b/src/redux/node/actions.ts index b28e8cd9..c7d93f21 100644 --- a/src/redux/node/actions.ts +++ b/src/redux/node/actions.ts @@ -50,15 +50,6 @@ export const nodePostLocalComment = ( type: NODE_ACTIONS.POST_COMMENT, }); -export const nodeSubmitLocal = ( - node: INode, - callback: (e?: string, errors?: Record) => void -) => ({ - node, - callback, - type: NODE_ACTIONS.SUBMIT_LOCAL, -}); - export const nodeSetSendingComment = (is_sending_comment: boolean) => ({ is_sending_comment, type: NODE_ACTIONS.SET_SENDING_COMMENT, diff --git a/src/redux/node/api.ts b/src/redux/node/api.ts index 8aab83d0..c2bb458c 100644 --- a/src/redux/node/api.ts +++ b/src/redux/node/api.ts @@ -42,9 +42,6 @@ export type ApiGetNodeCommentsResponse = { comments: IComment[]; comment_count: export const apiPostNode = ({ node }: ApiPostNodeRequest) => api.post(API.NODE.SAVE, node).then(cleanResult); -export const apiPostNodeLocal = ({ node }: ApiPostNodeRequest) => - api.post(API.NODE.SAVE, node).then(cleanResult); - export const getNodeDiff = ({ start, end, @@ -69,7 +66,10 @@ export const getNodeDiff = ({ .then(cleanResult); export const apiGetNode = ({ id }: ApiGetNodeRequest, config?: AxiosRequestConfig) => - api.get(API.NODE.GET_NODE(id), config).then(cleanResult); + api + .get(API.NODE.GET_NODE(id), config) + .then(cleanResult) + .then(data => ({ node: data.node, last_seen: data.last_seen })); export const apiGetNodeWithCancel = ({ id }: ApiGetNodeRequest) => { const cancelToken = axios.CancelToken.source(); diff --git a/src/redux/node/constants.ts b/src/redux/node/constants.ts index 950a5eaf..5fdd5868 100644 --- a/src/redux/node/constants.ts +++ b/src/redux/node/constants.ts @@ -22,11 +22,9 @@ import { LabPad } from '~/components/lab/LabPad'; import { LabDescription } from '~/components/lab/LabDescription'; import { LabVideo } from '~/components/lab/LabVideo'; import { LabAudio } from '~/components/lab/LabAudioBlock'; -import { LabLine } from '~/components/lab/LabLine'; const prefix = 'NODE.'; export const NODE_ACTIONS = { - SUBMIT_LOCAL: `${prefix}SUBMIT_LOCAL`, LOAD_NODE: `${prefix}LOAD_NODE`, GOTO_NODE: `${prefix}GOTO_NODE`, SET: `${prefix}SET`, diff --git a/src/redux/node/sagas.ts b/src/redux/node/sagas.ts index f366d1aa..7cc8cae2 100644 --- a/src/redux/node/sagas.ts +++ b/src/redux/node/sagas.ts @@ -19,7 +19,6 @@ import { nodeSetLoading, nodeSetLoadingComments, nodeSetTags, - nodeSubmitLocal, nodeUpdateTags, } from './actions'; import { @@ -29,7 +28,6 @@ import { apiLockComment, apiLockNode, apiPostComment, - apiPostNode, apiPostNodeHeroic, apiPostNodeLike, apiPostNodeTags, @@ -45,6 +43,7 @@ import { DIALOGS } from '~/redux/modal/constants'; import { has } from 'ramda'; import { selectLabListNodes } from '~/redux/lab/selectors'; import { labSetList } from '~/redux/lab/actions'; +import { apiPostNode } from '~/redux/node/api'; export function* updateNodeEverywhere(node) { const { @@ -66,42 +65,6 @@ export function* updateNodeEverywhere(node) { ); } -function* onNodeSubmitLocal({ node, callback }: ReturnType) { - try { - const { errors, node: result }: Unwrap = yield call(apiPostNode, { node }); - - if (errors && Object.values(errors).length > 0) { - callback('', errors); - return; - } - - if (node.is_promoted) { - const nodes: ReturnType = yield select(selectFlowNodes); - const updated_flow_nodes = node.id - ? nodes.map(item => (item.id === result.id ? result : item)) - : [result, ...nodes]; - yield put(flowSetNodes(updated_flow_nodes)); - } else { - const nodes: ReturnType = yield select(selectLabListNodes); - const updated_lab_nodes = node.id - ? nodes.map(item => (item.node.id === result.id ? { ...item, node: result } : item)) - : [{ node: result, comment_count: 0, last_seen: node.created_at }, ...nodes]; - yield put(labSetList({ nodes: updated_lab_nodes })); - } - - const { current } = yield select(selectNode); - - if (node.id && current.id === result.id) { - yield put(nodeSetCurrent(result)); - } - - callback(); - return; - } catch (error) { - callback(error.message); - } -} - function* onNodeGoto({ id, node_type }: ReturnType) { if (!id) { return; @@ -369,7 +332,6 @@ function* onLockCommentSaga({ id, is_locked }: ReturnType { + const dispatch = useDispatch(); + const flowNodes = useShallowSelect(selectFlowNodes); + const labNodes = useShallowSelect(selectLabListNodes); + + return useCallback( + async (node: INode) => { + const result = await apiPostNode({ node }); + + // TODO: use another store here someday + if (node.is_promoted) { + const updatedNodes = [result.node, ...flowNodes]; + dispatch(flowSetNodes(updatedNodes)); + } else { + const updatedNodes = [ + { node: result.node, comment_count: 0, last_seen: node.created_at }, + ...labNodes, + ]; + dispatch(labSetList({ nodes: updatedNodes })); + } + }, + [flowNodes, labNodes, dispatch] + ); +}; diff --git a/src/utils/hooks/data/useGetNode.ts b/src/utils/hooks/data/useGetNode.ts index adb22903..0db9b865 100644 --- a/src/utils/hooks/data/useGetNode.ts +++ b/src/utils/hooks/data/useGetNode.ts @@ -1,19 +1,29 @@ -import { INode } from '~/redux/types'; import useSWR from 'swr'; -import { AxiosResponse } from 'axios'; import { ApiGetNodeResponse } from '~/redux/node/types'; import { API } from '~/constants/api'; -import { api } from '~/utils/api'; +import { useOnNodeSeen } from '~/utils/hooks/node/useOnNodeSeen'; +import { apiGetNode } from '~/redux/node/api'; +import { useCallback } from 'react'; +import { INode } from '~/redux/types'; -export const useGetNode = (id?: INode['id']) => { - const { data, isValidating: isLoading } = useSWR>( - API.NODE.GET_NODE(id || ''), - api.get +export const useGetNode = (id: number) => { + const { data, isValidating, mutate } = useSWR(API.NODE.GET_NODE(id), () => + apiGetNode({ id }) ); - if (!id) { - return { node: undefined, isLoading: false }; - } + const update = useCallback( + async (node?: Partial) => { + if (!data?.node) { + await mutate(); + return; + } - return { node: data?.data.node, isLoading }; + await mutate({ node: { ...data.node, ...node } }, true); + }, + [data, mutate] + ); + + useOnNodeSeen(data?.node); + + return { node: data?.node, isLoading: isValidating && !data, update }; }; diff --git a/src/utils/hooks/data/useUpdateNode.ts b/src/utils/hooks/data/useUpdateNode.ts new file mode 100644 index 00000000..6a9a8277 --- /dev/null +++ b/src/utils/hooks/data/useUpdateNode.ts @@ -0,0 +1,43 @@ +import { useGetNode } from '~/utils/hooks/data/useGetNode'; +import { useCallback } from 'react'; +import { INode } from '~/redux/types'; +import { apiPostNode } from '~/redux/node/api'; +import { selectFlowNodes } from '~/redux/flow/selectors'; +import { flowSetNodes } from '~/redux/flow/actions'; +import { selectLabListNodes } from '~/redux/lab/selectors'; +import { labSetList } from '~/redux/lab/actions'; +import { useShallowSelect } from '~/utils/hooks/useShallowSelect'; +import { useDispatch } from 'react-redux'; + +export const useUpdateNode = (id: number) => { + const dispatch = useDispatch(); + const { update } = useGetNode(id); + const flowNodes = useShallowSelect(selectFlowNodes); + const labNodes = useShallowSelect(selectLabListNodes); + + return useCallback( + async (node: INode) => { + const result = await apiPostNode({ node }); + + if (!update) { + return; + } + + await update(result.node); + + // TODO: use another store here someday + if (node.is_promoted) { + const updatedNodes = flowNodes.map(item => + item.id === result.node.id ? result.node : item + ); + dispatch(flowSetNodes(updatedNodes)); + } else { + const updatedNodes = labNodes.map(item => + item.node.id === result.node.id ? { ...item, node: result.node } : item + ); + dispatch(labSetList({ nodes: updatedNodes })); + } + }, + [update, flowNodes, dispatch, labNodes] + ); +}; diff --git a/src/utils/hooks/node/useFullNode.ts b/src/utils/hooks/node/useFullNode.ts index 2448e608..2f5e91dd 100644 --- a/src/utils/hooks/node/useFullNode.ts +++ b/src/utils/hooks/node/useFullNode.ts @@ -13,8 +13,8 @@ export const useFullNode = (id: string) => { lastSeenCurrent, } = useShallowSelect(selectNode); - useLoadNode(id); - useOnNodeSeen(node); + // useLoadNode(id); + // useOnNodeSeen(node); return { node, comments, commentsCount, lastSeenCurrent, isLoading, isLoadingComments }; }; diff --git a/src/utils/hooks/useNodeFormFormik.ts b/src/utils/hooks/node/useNodeFormFormik.ts similarity index 52% rename from src/utils/hooks/useNodeFormFormik.ts rename to src/utils/hooks/node/useNodeFormFormik.ts index 37e3745e..919a424c 100644 --- a/src/utils/hooks/useNodeFormFormik.ts +++ b/src/utils/hooks/node/useNodeFormFormik.ts @@ -1,15 +1,13 @@ import { INode } from '~/redux/types'; import { FileUploader } from '~/utils/hooks/useFileUploader'; -import { useCallback, useEffect, useRef } from 'react'; -import { FormikHelpers, useFormik, useFormikContext } from 'formik'; +import { useCallback, useRef } from 'react'; +import { FormikConfig, FormikHelpers, useFormik, useFormikContext } from 'formik'; import { object } from 'yup'; -import { useDispatch } from 'react-redux'; -import { nodeSubmitLocal } from '~/redux/node/actions'; import { keys } from 'ramda'; const validationSchema = object().shape({}); -const onSuccess = ({ resetForm, setStatus, setSubmitting, setErrors }: FormikHelpers) => ( +const afterSubmit = ({ resetForm, setStatus, setSubmitting, setErrors }: FormikHelpers) => ( e?: string, errors?: Record ) => { @@ -33,17 +31,9 @@ const onSuccess = ({ resetForm, setStatus, setSubmitting, setErrors }: FormikHel export const useNodeFormFormik = ( values: INode, uploader: FileUploader, - stopEditing: () => void + stopEditing: () => void, + sendSaveRequest: (node: INode) => Promise ) => { - const dispatch = useDispatch(); - const onSubmit = useCallback( - (values: INode, helpers: FormikHelpers) => { - helpers.setSubmitting(true); - dispatch(nodeSubmitLocal(values, onSuccess(helpers))); - }, - [dispatch] - ); - const { current: initialValues } = useRef(values); const onReset = useCallback(() => { @@ -52,7 +42,19 @@ export const useNodeFormFormik = ( if (stopEditing) stopEditing(); }, [uploader, stopEditing]); - const formik = useFormik({ + const onSubmit = useCallback['onSubmit']>( + async (values, helpers) => { + try { + await sendSaveRequest({ ...values, files: uploader.files }); + afterSubmit(helpers)(); + } catch (error) { + afterSubmit(helpers)(error?.response?.data?.error, error?.response?.data?.errors); + } + }, + [sendSaveRequest, uploader.files] + ); + + return useFormik({ initialValues, validationSchema, onSubmit, @@ -60,17 +62,6 @@ export const useNodeFormFormik = ( initialStatus: '', validateOnChange: true, }); - - useEffect( - () => { - formik.setFieldValue('files', uploader.files); - }, - // because it breaks files logic - // eslint-disable-next-line - [uploader.files, formik.setFieldValue] - ); - - return formik; }; export const useNodeFormContext = () => useFormikContext(); diff --git a/src/utils/hooks/node/useNodeImages.ts b/src/utils/hooks/node/useNodeImages.ts index 375d6198..4f6b71d5 100644 --- a/src/utils/hooks/node/useNodeImages.ts +++ b/src/utils/hooks/node/useNodeImages.ts @@ -3,10 +3,6 @@ import { useMemo } from 'react'; import { UPLOAD_TYPES } from '~/redux/uploads/constants'; export const useNodeImages = (node: INode) => { - if (!node?.files) { - return []; - } - return useMemo(() => node.files.filter(file => file && file.type === UPLOAD_TYPES.IMAGE), [ node.files, ]); diff --git a/src/utils/hooks/node/useNodePermissions.ts b/src/utils/hooks/node/useNodePermissions.ts index 4f93ba78..08eea085 100644 --- a/src/utils/hooks/node/useNodePermissions.ts +++ b/src/utils/hooks/node/useNodePermissions.ts @@ -4,7 +4,7 @@ import { useShallowSelect } from '~/utils/hooks/useShallowSelect'; import { selectUser } from '~/redux/auth/selectors'; import { INode } from '~/redux/types'; -export const useNodePermissions = (node: INode) => { +export const useNodePermissions = (node?: INode) => { const user = useShallowSelect(selectUser); const edit = useMemo(() => canEditNode(node, user), [node, user]); const like = useMemo(() => canLikeNode(node, user), [node, user]); diff --git a/src/utils/hooks/node/useNodeTags.ts b/src/utils/hooks/node/useNodeTags.ts index 0780360a..00cfe620 100644 --- a/src/utils/hooks/node/useNodeTags.ts +++ b/src/utils/hooks/node/useNodeTags.ts @@ -2,18 +2,26 @@ import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router'; import { useCallback } from 'react'; import { nodeDeleteTag, nodeUpdateTags } from '~/redux/node/actions'; -import { INode, ITag } from '~/redux/types'; +import { ITag } from '~/redux/types'; import { URLS } from '~/constants/urls'; +import { useGetNode } from '~/utils/hooks/data/useGetNode'; +import { apiDeleteNodeTag, apiPostNodeTags } from '~/redux/node/api'; -export const useNodeTags = (id: INode['id']) => { +export const useNodeTags = (id: number) => { + const { update } = useGetNode(id); const dispatch = useDispatch(); const history = useHistory(); const onChange = useCallback( - (tags: string[]) => { - dispatch(nodeUpdateTags(id, tags)); + async (tags: string[]) => { + try { + const result = await apiPostNodeTags({ id, tags }); + await update({ tags: result.node.tags }); + } catch (error) { + console.warn(error); + } }, - [dispatch, id] + [id, update] ); const onClick = useCallback( @@ -28,10 +36,15 @@ export const useNodeTags = (id: INode['id']) => { ); const onDelete = useCallback( - (tagId: ITag['ID']) => { - dispatch(nodeDeleteTag(id, tagId)); + async (tagId: ITag['ID']) => { + try { + const result = await apiDeleteNodeTag({ id, tagId }); + await update({ tags: result.tags }); + } catch (e) { + console.warn(e); + } }, - [dispatch, id] + [id, update] ); return { onDelete, onChange, onClick }; diff --git a/src/utils/hooks/node/useOnNodeSeen.ts b/src/utils/hooks/node/useOnNodeSeen.ts index f9e87d9e..bf91647a 100644 --- a/src/utils/hooks/node/useOnNodeSeen.ts +++ b/src/utils/hooks/node/useOnNodeSeen.ts @@ -2,15 +2,22 @@ import { INode } from '~/redux/types'; import { useDispatch } from 'react-redux'; import { labSeenNode } from '~/redux/lab/actions'; import { flowSeenNode } from '~/redux/flow/actions'; +import { useEffect } from 'react'; // useOnNodeSeen updates node seen status across all needed places -export const useOnNodeSeen = (node: INode) => { +export const useOnNodeSeen = (node?: INode) => { const dispatch = useDispatch(); - // Remove node from updated - if (node.is_promoted) { - dispatch(flowSeenNode(node.id)); - } else { - dispatch(labSeenNode(node.id)); - } + useEffect(() => { + if (!node?.id) { + return; + } + + // Remove node from updated + if (node.is_promoted) { + dispatch(flowSeenNode(node.id)); + } else { + dispatch(labSeenNode(node.id)); + } + }, [dispatch, node]); }; diff --git a/src/utils/node.ts b/src/utils/node.ts index 0e2f426e..a9d2ba03 100644 --- a/src/utils/node.ts +++ b/src/utils/node.ts @@ -4,18 +4,17 @@ import { IUser } from '~/redux/auth/types'; import { path } from 'ramda'; import { NODE_TYPES } from '~/redux/node/constants'; -export const canEditNode = (node: Partial, user: Partial): boolean => +export const canEditNode = (node?: Partial, user?: Partial): boolean => path(['role'], user) === USER_ROLES.ADMIN || (path(['user', 'id'], node) && path(['user', 'id'], node) === path(['id'], user)); -export const canEditComment = (comment: Partial, user: Partial): boolean => - path(['role'], user) === USER_ROLES.ADMIN || - (path(['user', 'id'], comment) && path(['user', 'id'], comment) === path(['id'], user)); +export const canEditComment = (comment?: Partial, user?: Partial): boolean => + path(['role'], user) === USER_ROLES.ADMIN || path(['user', 'id'], comment) === path(['id'], user); -export const canLikeNode = (node: Partial, user: Partial): boolean => - path(['role'], user) && path(['role'], user) !== USER_ROLES.GUEST; +export const canLikeNode = (node?: Partial, user?: Partial): boolean => + path(['role'], user) !== USER_ROLES.GUEST; -export const canStarNode = (node: Partial, user: Partial): boolean => - (node.type === NODE_TYPES.IMAGE || node.is_promoted === false) && - path(['role'], user) && +export const canStarNode = (node?: Partial, user?: Partial): boolean => + path(['type'], node) === NODE_TYPES.IMAGE && + path(['is_promoted'], node) === false && path(['role'], user) === USER_ROLES.ADMIN;