mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
fixed node actions
This commit is contained in:
parent
e8effb92f1
commit
0bc2ff250f
13 changed files with 107 additions and 201 deletions
|
@ -1,32 +0,0 @@
|
|||
import React, { FC, memo } from 'react';
|
||||
import { INode } from '~/redux/types';
|
||||
import { NodePanelInner } from '~/components/node/NodePanelInner';
|
||||
import { useNodePermissions } from '~/utils/hooks/node/useNodePermissions';
|
||||
import { useNodeActions } from '~/utils/hooks/node/useNodeActions';
|
||||
import { shallowEqual } from 'react-redux';
|
||||
|
||||
interface IProps {
|
||||
node: INode;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const NodePanel: FC<IProps> = memo(({ node, isLoading }) => {
|
||||
const [can_edit, can_like, can_star] = useNodePermissions(node);
|
||||
const { onEdit, onLike, onStar, onLock } = useNodeActions(node);
|
||||
|
||||
return (
|
||||
<NodePanelInner
|
||||
node={node}
|
||||
onEdit={onEdit}
|
||||
onLike={onLike}
|
||||
onStar={onStar}
|
||||
onLock={onLock}
|
||||
canEdit={can_edit}
|
||||
canLike={can_like}
|
||||
canStar={can_star}
|
||||
isLoading={!!isLoading}
|
||||
/>
|
||||
);
|
||||
}, shallowEqual);
|
||||
|
||||
export { NodePanel };
|
|
@ -1,7 +1,6 @@
|
|||
import React, { FC, memo } from 'react';
|
||||
import React, { VFC, memo } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { INode } from '~/redux/types';
|
||||
import classNames from 'classnames';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { getPrettyDate } from '~/utils/dom';
|
||||
|
@ -9,8 +8,15 @@ import { URLS } from '~/constants/urls';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
interface IProps {
|
||||
node: Partial<INode>;
|
||||
stack?: boolean;
|
||||
id?: number;
|
||||
title: string;
|
||||
username?: string;
|
||||
createdAt: string;
|
||||
likeCount: number;
|
||||
|
||||
isHeroic: boolean;
|
||||
isLocked: boolean;
|
||||
isLiked: boolean;
|
||||
|
||||
canEdit: boolean;
|
||||
canLike: boolean;
|
||||
|
@ -24,10 +30,17 @@ interface IProps {
|
|||
onLock: () => void;
|
||||
}
|
||||
|
||||
const NodePanelInner: FC<IProps> = memo(
|
||||
const NodePanelInner: VFC<IProps> = memo(
|
||||
({
|
||||
node: { id, title, user, is_liked, is_heroic, deleted_at, created_at, like_count },
|
||||
stack,
|
||||
id,
|
||||
title,
|
||||
username,
|
||||
createdAt,
|
||||
likeCount,
|
||||
|
||||
isHeroic,
|
||||
isLocked,
|
||||
isLiked,
|
||||
|
||||
canStar,
|
||||
canEdit,
|
||||
|
@ -41,19 +54,19 @@ const NodePanelInner: FC<IProps> = memo(
|
|||
onLock,
|
||||
}) => {
|
||||
return (
|
||||
<div className={classNames(styles.wrap, { stack })}>
|
||||
<div className={classNames(styles.wrap)}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.panel}>
|
||||
<div className={styles.title}>
|
||||
{isLoading ? <Placeholder width="40%" /> : title || '...'}
|
||||
</div>
|
||||
|
||||
{user && user.username && (
|
||||
{!!username && (
|
||||
<div className={styles.name}>
|
||||
{isLoading ? (
|
||||
<Placeholder width="100px" />
|
||||
) : (
|
||||
`~${user.username.toLocaleLowerCase()}, ${getPrettyDate(created_at)}`
|
||||
`~${username.toLocaleLowerCase()}, ${getPrettyDate(createdAt)}`
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
@ -67,8 +80,8 @@ const NodePanelInner: FC<IProps> = memo(
|
|||
|
||||
<div className={styles.editor_buttons}>
|
||||
{canStar && (
|
||||
<div className={classNames(styles.star, { is_heroic })}>
|
||||
{is_heroic ? (
|
||||
<div className={classNames(styles.star, { [styles.is_heroic]: isHeroic })}>
|
||||
{isHeroic ? (
|
||||
<Icon icon="star_full" size={24} onClick={onStar} />
|
||||
) : (
|
||||
<Icon icon="star" size={24} onClick={onStar} />
|
||||
|
@ -77,27 +90,29 @@ const NodePanelInner: FC<IProps> = memo(
|
|||
)}
|
||||
|
||||
<div>
|
||||
<Icon icon={deleted_at ? 'locked' : 'unlocked'} size={24} onClick={onLock} />
|
||||
<Icon icon={isLocked ? 'locked' : 'unlocked'} size={24} onClick={onLock} />
|
||||
</div>
|
||||
|
||||
<Link to={URLS.NODE_EDIT_URL(id)}>
|
||||
<Icon icon="edit" size={24} onClick={onEdit} />
|
||||
</Link>
|
||||
{!!id && (
|
||||
<Link to={URLS.NODE_EDIT_URL(id)}>
|
||||
<Icon icon="edit" size={24} onClick={onEdit} />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.buttons}>
|
||||
{canLike && (
|
||||
<div className={classNames(styles.like, { is_liked })}>
|
||||
{is_liked ? (
|
||||
<div className={classNames(styles.like, { [styles.is_liked]: isLiked })}>
|
||||
{isLiked ? (
|
||||
<Icon icon="heart_full" size={24} onClick={onLike} />
|
||||
) : (
|
||||
<Icon icon="heart" size={24} onClick={onLike} />
|
||||
)}
|
||||
|
||||
{!!like_count && like_count > 0 && (
|
||||
<div className={styles.like_count}>{like_count}</div>
|
||||
{!!likeCount && likeCount > 0 && (
|
||||
<div className={styles.like_count}>{likeCount}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -38,17 +38,6 @@
|
|||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
|
||||
&:global(.stack) {
|
||||
padding: 0 $gap;
|
||||
bottom: 0;
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
|
||||
@include tablet {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
|
@ -209,7 +198,7 @@
|
|||
position: relative;
|
||||
flex: 0 0 32px;
|
||||
|
||||
&:global(.is_liked) {
|
||||
.is_liked {
|
||||
svg {
|
||||
fill: $red;
|
||||
}
|
||||
|
@ -249,7 +238,7 @@
|
|||
transition: fill, stroke 0.25s;
|
||||
will-change: transform;
|
||||
|
||||
&:global(.is_heroic) {
|
||||
.is_heroic {
|
||||
svg {
|
||||
fill: $orange;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import React, { FC } from 'react';
|
|||
import { Route } from 'react-router';
|
||||
import { Card } from '~/components/containers/Card';
|
||||
|
||||
import { NodePanel } from '~/components/node/NodePanel';
|
||||
import { Footer } from '~/components/main/Footer';
|
||||
|
||||
import { SidebarRouter } from '~/containers/main/SidebarRouter';
|
||||
|
@ -15,12 +14,17 @@ import { EditorEditDialog } from '~/containers/dialogs/EditorEditDialog';
|
|||
|
||||
import styles from './styles.module.scss';
|
||||
import { useNodeContext } from '~/utils/context/NodeContextProvider';
|
||||
import { useNodePermissions } from '~/utils/hooks/node/useNodePermissions';
|
||||
import { useNodeActions } from '~/utils/hooks/node/useNodeActions';
|
||||
import { NodePanelInner } from '~/components/node/NodePanelInner';
|
||||
|
||||
type IProps = {};
|
||||
|
||||
const NodeLayout: FC<IProps> = () => {
|
||||
const { node, isLoading } = useNodeContext();
|
||||
const { node, isLoading, update } = useNodeContext();
|
||||
const { head, block } = useNodeBlocks(node, isLoading);
|
||||
const [canEdit, canLike, canStar] = useNodePermissions(node);
|
||||
const { onEdit, onLike, onStar, onLock } = useNodeActions(node, update);
|
||||
|
||||
useNodeCoverImage(node);
|
||||
|
||||
|
@ -33,7 +37,24 @@ const NodeLayout: FC<IProps> = () => {
|
|||
{block}
|
||||
|
||||
<div className={styles.panel}>
|
||||
<NodePanel node={node} isLoading={isLoading} />
|
||||
<NodePanelInner
|
||||
id={node.id}
|
||||
title={node.title}
|
||||
username={node.user?.username}
|
||||
likeCount={node?.like_count || 0}
|
||||
isHeroic={!!node.is_promoted}
|
||||
isLiked={!!node.is_liked}
|
||||
isLocked={!!node.deleted_at}
|
||||
isLoading={isLoading}
|
||||
createdAt={node.created_at || ''}
|
||||
canEdit={canEdit}
|
||||
canLike={canLike}
|
||||
canStar={canStar}
|
||||
onEdit={onEdit}
|
||||
onLike={onLike}
|
||||
onStar={onStar}
|
||||
onLock={onLock}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<NodeBottomBlock commentsOrder="DESC" />
|
||||
|
|
|
@ -9,12 +9,12 @@ import { useImageModal } from '~/utils/hooks/useImageModal';
|
|||
import { useNodeComments } from '~/utils/hooks/node/useNodeComments';
|
||||
import { useBoris } from '~/utils/hooks/boris/useBoris';
|
||||
import { NodeContextProvider } from '~/utils/context/NodeContextProvider';
|
||||
import { useGetNode } from '~/utils/hooks/data/useGetNode';
|
||||
|
||||
const BorisPage: VFC = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { node, isLoading, update } = useGetNode(696);
|
||||
const {
|
||||
current,
|
||||
is_loading,
|
||||
comments,
|
||||
comment_count: count,
|
||||
is_loading_comments: isLoadingComments,
|
||||
|
@ -28,7 +28,7 @@ const BorisPage: VFC = () => {
|
|||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<NodeContextProvider node={current} isLoading={is_loading}>
|
||||
<NodeContextProvider node={node} isLoading={isLoading} update={update}>
|
||||
<CommentContextProvider
|
||||
comments={comments}
|
||||
count={count}
|
||||
|
|
|
@ -21,7 +21,7 @@ const NodePage: FC<Props> = ({
|
|||
params: { id },
|
||||
},
|
||||
}) => {
|
||||
const { node, isLoading } = useGetNode(parseInt(id, 10));
|
||||
const { node, isLoading, update } = useGetNode(parseInt(id, 10));
|
||||
const { isLoadingComments, comments, commentsCount, lastSeenCurrent } = useFullNode(id);
|
||||
|
||||
const onShowImageModal = useImageModal();
|
||||
|
@ -40,7 +40,7 @@ const NodePage: FC<Props> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<NodeContextProvider node={node} isLoading={isLoading}>
|
||||
<NodeContextProvider node={node} isLoading={isLoading} update={update}>
|
||||
<NodeRelatedProvider id={parseInt(id, 10)} tags={node.tags}>
|
||||
<CommentContextProvider
|
||||
comments={comments}
|
||||
|
|
|
@ -7,11 +7,6 @@ export const nodeSet = (node: Partial<INodeState>) => ({
|
|||
type: NODE_ACTIONS.SET,
|
||||
});
|
||||
|
||||
export const nodeSetSaveErrors = (errors: IValidationErrors) => ({
|
||||
errors,
|
||||
type: NODE_ACTIONS.SET_SAVE_ERRORS,
|
||||
});
|
||||
|
||||
export const nodeGotoNode = (id: INode['id'], node_type: INode['type']) => ({
|
||||
id,
|
||||
node_type,
|
||||
|
@ -60,22 +55,6 @@ export const nodeSetComments = (comments: IComment[]) => ({
|
|||
type: NODE_ACTIONS.SET_COMMENTS,
|
||||
});
|
||||
|
||||
export const nodeLike = (id: INode['id']) => ({
|
||||
type: NODE_ACTIONS.LIKE,
|
||||
id,
|
||||
});
|
||||
|
||||
export const nodeStar = (id: INode['id']) => ({
|
||||
type: NODE_ACTIONS.STAR,
|
||||
id,
|
||||
});
|
||||
|
||||
export const nodeLock = (id: INode['id'], is_locked: boolean) => ({
|
||||
type: NODE_ACTIONS.LOCK,
|
||||
id,
|
||||
is_locked,
|
||||
});
|
||||
|
||||
export const nodeLockComment = (id: number, is_locked: boolean, nodeId: number) => ({
|
||||
type: NODE_ACTIONS.LOCK_COMMENT,
|
||||
nodeId,
|
||||
|
@ -83,11 +62,6 @@ export const nodeLockComment = (id: number, is_locked: boolean, nodeId: number)
|
|||
is_locked,
|
||||
});
|
||||
|
||||
export const nodeSetEditor = (editor: INode) => ({
|
||||
type: NODE_ACTIONS.SET_EDITOR,
|
||||
editor,
|
||||
});
|
||||
|
||||
export const nodeSetCoverImage = (current_cover_image?: IFile) => ({
|
||||
type: NODE_ACTIONS.SET_COVER_IMAGE,
|
||||
current_cover_image,
|
||||
|
|
|
@ -29,23 +29,17 @@ export const NODE_ACTIONS = {
|
|||
GOTO_NODE: `${prefix}GOTO_NODE`,
|
||||
SET: `${prefix}SET`,
|
||||
|
||||
LIKE: `${prefix}LIKE`,
|
||||
STAR: `${prefix}STAR`,
|
||||
LOCK: `${prefix}LOCK`,
|
||||
LOCK_COMMENT: `${prefix}LOCK_COMMENT`,
|
||||
EDIT_COMMENT: `${prefix}EDIT_COMMENT`,
|
||||
LOAD_MORE_COMMENTS: `${prefix}LOAD_MORE_COMMENTS`,
|
||||
|
||||
SET_SAVE_ERRORS: `${prefix}SET_SAVE_ERRORS`,
|
||||
SET_LOADING: `${prefix}SET_LOADING`,
|
||||
SET_LOADING_COMMENTS: `${prefix}SET_LOADING_COMMENTS`,
|
||||
SET_SENDING_COMMENT: `${prefix}SET_SENDING_COMMENT`,
|
||||
SET_CURRENT: `${prefix}SET_CURRENT`,
|
||||
SET_EDITOR: `${prefix}SET_EDITOR`,
|
||||
|
||||
POST_LOCAL_COMMENT: `${prefix}POST_LOCAL_COMMENT`,
|
||||
SET_COMMENTS: `${prefix}SET_COMMENTS`,
|
||||
SET_RELATED: `${prefix}SET_RELATED`,
|
||||
|
||||
SET_COVER_IMAGE: `${prefix}SET_COVER_IMAGE`,
|
||||
};
|
||||
|
|
|
@ -5,10 +5,8 @@ import {
|
|||
nodeSetComments,
|
||||
nodeSetCoverImage,
|
||||
nodeSetCurrent,
|
||||
nodeSetEditor,
|
||||
nodeSetLoading,
|
||||
nodeSetLoadingComments,
|
||||
nodeSetSaveErrors,
|
||||
nodeSetSendingComment,
|
||||
} from './actions';
|
||||
import { INodeState } from './reducer';
|
||||
|
@ -18,9 +16,6 @@ const setData = (state: INodeState, { node }: ReturnType<typeof nodeSet>) => ({
|
|||
...node,
|
||||
});
|
||||
|
||||
const setSaveErrors = (state: INodeState, { errors }: ReturnType<typeof nodeSetSaveErrors>) =>
|
||||
assocPath(['errors'], errors, state);
|
||||
|
||||
const setLoading = (state: INodeState, { is_loading }: ReturnType<typeof nodeSetLoading>) =>
|
||||
assocPath(['is_loading'], is_loading, state);
|
||||
|
||||
|
@ -40,9 +35,6 @@ const setSendingComment = (
|
|||
const setComments = (state: INodeState, { comments }: ReturnType<typeof nodeSetComments>) =>
|
||||
assocPath(['comments'], comments, state);
|
||||
|
||||
const setEditor = (state: INodeState, { editor }: ReturnType<typeof nodeSetEditor>) =>
|
||||
assocPath(['editor'], editor, state);
|
||||
|
||||
const setCoverImage = (
|
||||
state: INodeState,
|
||||
{ current_cover_image }: ReturnType<typeof nodeSetCoverImage>
|
||||
|
@ -50,12 +42,10 @@ const setCoverImage = (
|
|||
|
||||
export const NODE_HANDLERS = {
|
||||
[NODE_ACTIONS.SET]: setData,
|
||||
[NODE_ACTIONS.SET_SAVE_ERRORS]: setSaveErrors,
|
||||
[NODE_ACTIONS.SET_LOADING]: setLoading,
|
||||
[NODE_ACTIONS.SET_LOADING_COMMENTS]: setLoadingComments,
|
||||
[NODE_ACTIONS.SET_CURRENT]: setCurrent,
|
||||
[NODE_ACTIONS.SET_SENDING_COMMENT]: setSendingComment,
|
||||
[NODE_ACTIONS.SET_COMMENTS]: setComments,
|
||||
[NODE_ACTIONS.SET_EDITOR]: setEditor,
|
||||
[NODE_ACTIONS.SET_COVER_IMAGE]: setCoverImage,
|
||||
};
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { call, put, select, takeLatest, takeLeading } from 'redux-saga/effects';
|
||||
|
||||
import { COMMENTS_DISPLAY, EMPTY_NODE, NODE_ACTIONS, NODE_EDITOR_DATA } from './constants';
|
||||
import { COMMENTS_DISPLAY, EMPTY_NODE, NODE_ACTIONS } from './constants';
|
||||
import {
|
||||
nodeGotoNode,
|
||||
nodeLike,
|
||||
nodeLoadNode,
|
||||
nodeLock,
|
||||
nodeLockComment,
|
||||
nodePostLocalComment,
|
||||
nodeSet,
|
||||
|
@ -13,27 +11,11 @@ import {
|
|||
nodeSetCurrent,
|
||||
nodeSetLoadingComments,
|
||||
} from './actions';
|
||||
import {
|
||||
apiGetNodeComments,
|
||||
apiLockComment,
|
||||
apiLockNode,
|
||||
apiPostComment,
|
||||
apiPostNodeHeroic,
|
||||
apiPostNodeLike,
|
||||
apiPostNodeTags,
|
||||
} from './api';
|
||||
import { apiGetNodeComments, apiLockComment, apiPostComment } from './api';
|
||||
import { flowSetNodes } from '../flow/actions';
|
||||
import { modalSetShown, modalShowDialog } from '../modal/actions';
|
||||
import { selectFlowNodes } from '../flow/selectors';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { selectNode } from './selectors';
|
||||
import { INode, Unwrap } from '../types';
|
||||
import { NODE_EDITOR_DIALOGS } from '~/constants/dialogs';
|
||||
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';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
|
||||
export function* updateNodeEverywhere(node) {
|
||||
|
@ -156,63 +138,6 @@ function* onPostComment({ nodeId, comment, callback }: ReturnType<typeof nodePos
|
|||
}
|
||||
}
|
||||
|
||||
function* onLikeSaga({ id }: ReturnType<typeof nodeLike>) {
|
||||
const { current }: ReturnType<typeof selectNode> = yield select(selectNode);
|
||||
|
||||
try {
|
||||
const count = current.like_count || 0;
|
||||
|
||||
yield call(updateNodeEverywhere, {
|
||||
...current,
|
||||
is_liked: !current.is_liked,
|
||||
like_count: current.is_liked ? Math.max(count - 1, 0) : count + 1,
|
||||
});
|
||||
|
||||
const data: Unwrap<typeof apiPostNodeLike> = yield call(apiPostNodeLike, { id });
|
||||
|
||||
yield call(updateNodeEverywhere, {
|
||||
...current,
|
||||
is_liked: data.is_liked,
|
||||
like_count: data.is_liked ? count + 1 : Math.max(count - 1, 0),
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function* onStarSaga({ id }: ReturnType<typeof nodeLike>) {
|
||||
try {
|
||||
const {
|
||||
current,
|
||||
current: { is_heroic },
|
||||
} = yield select(selectNode);
|
||||
|
||||
yield call(updateNodeEverywhere, { ...current, is_heroic: !is_heroic });
|
||||
|
||||
const data: Unwrap<typeof apiPostNodeHeroic> = yield call(apiPostNodeHeroic, { id });
|
||||
|
||||
yield call(updateNodeEverywhere, { ...current, is_heroic: data.is_heroic });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function* onLockSaga({ id, is_locked }: ReturnType<typeof nodeLock>) {
|
||||
const { current }: ReturnType<typeof selectNode> = yield select(selectNode);
|
||||
|
||||
try {
|
||||
yield call(updateNodeEverywhere, {
|
||||
...current,
|
||||
deleted_at: is_locked ? new Date().toISOString() : null,
|
||||
});
|
||||
|
||||
const data: Unwrap<typeof apiLockNode> = yield call(apiLockNode, { id, is_locked });
|
||||
|
||||
yield call(updateNodeEverywhere, {
|
||||
...current,
|
||||
deleted_at: data.deleted_at || undefined,
|
||||
});
|
||||
} catch {
|
||||
yield call(updateNodeEverywhere, { ...current, deleted_at: current.deleted_at });
|
||||
}
|
||||
}
|
||||
|
||||
function* onLockCommentSaga({ nodeId, id, is_locked }: ReturnType<typeof nodeLockComment>) {
|
||||
const { comments }: ReturnType<typeof selectNode> = yield select(selectNode);
|
||||
|
||||
|
@ -239,9 +164,6 @@ export default function* nodeSaga() {
|
|||
yield takeLatest(NODE_ACTIONS.GOTO_NODE, onNodeGoto);
|
||||
yield takeLatest(NODE_ACTIONS.LOAD_NODE, onNodeLoad);
|
||||
yield takeLatest(NODE_ACTIONS.POST_LOCAL_COMMENT, onPostComment);
|
||||
yield takeLatest(NODE_ACTIONS.LIKE, onLikeSaga);
|
||||
yield takeLatest(NODE_ACTIONS.STAR, onStarSaga);
|
||||
yield takeLatest(NODE_ACTIONS.LOCK, onLockSaga);
|
||||
yield takeLatest(NODE_ACTIONS.LOCK_COMMENT, onLockCommentSaga);
|
||||
yield takeLeading(NODE_ACTIONS.LOAD_MORE_COMMENTS, onNodeLoadMoreComments);
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@ import React, { createContext, FC, useContext } from 'react';
|
|||
|
||||
export interface NodeContextProps {
|
||||
node: INode;
|
||||
update: (node: Partial<INode>) => Promise<unknown>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export const NodeContext = createContext<NodeContextProps>({
|
||||
node: EMPTY_NODE,
|
||||
update: async () => {},
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useOnNodeSeen } from '~/utils/hooks/node/useOnNodeSeen';
|
|||
import { apiGetNode } from '~/redux/node/api';
|
||||
import { useCallback } from 'react';
|
||||
import { INode } from '~/redux/types';
|
||||
import { EMPTY_NODE } from '~/redux/node/constants';
|
||||
|
||||
export const useGetNode = (id: number) => {
|
||||
const { data, isValidating, mutate } = useSWR<ApiGetNodeResponse>(API.NODE.GET_NODE(id), () =>
|
||||
|
@ -25,5 +26,5 @@ export const useGetNode = (id: number) => {
|
|||
|
||||
useOnNodeSeen(data?.node);
|
||||
|
||||
return { node: data?.node, isLoading: isValidating && !data, update };
|
||||
return { node: data?.node || EMPTY_NODE, isLoading: isValidating && !data, update };
|
||||
};
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { INode } from '~/redux/types';
|
||||
import { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { nodeLike, nodeLock, nodeStar } from '~/redux/node/actions';
|
||||
import { modalShowDialog } from '~/redux/modal/actions';
|
||||
import { NODE_EDITOR_DIALOGS } from '~/constants/dialogs';
|
||||
import { apiLockNode, apiPostNodeHeroic, apiPostNodeLike } from '~/redux/node/api';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
|
||||
export const useNodeActions = (node: INode) => {
|
||||
export const useNodeActions = (node: INode, update: (node: Partial<INode>) => Promise<unknown>) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onEdit = useCallback(() => {
|
||||
|
@ -16,9 +17,38 @@ export const useNodeActions = (node: INode) => {
|
|||
dispatch(modalShowDialog(NODE_EDITOR_DIALOGS[node.type]));
|
||||
}, [dispatch, node]);
|
||||
|
||||
const onLike = useCallback(() => dispatch(nodeLike(node.id)), [dispatch, node]);
|
||||
const onStar = useCallback(() => dispatch(nodeStar(node.id)), [dispatch, node]);
|
||||
const onLock = useCallback(() => dispatch(nodeLock(node.id, !node.deleted_at)), [dispatch, node]);
|
||||
const onLike = useCallback(async () => {
|
||||
try {
|
||||
const result = await apiPostNodeLike({ id: node.id });
|
||||
const likeCount = node.like_count || 0;
|
||||
|
||||
if (result.is_liked) {
|
||||
await update({ like_count: likeCount + 1 });
|
||||
} else {
|
||||
await update({ like_count: likeCount - 1 });
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
}
|
||||
}, [node.id, node.like_count, update]);
|
||||
|
||||
const onStar = useCallback(async () => {
|
||||
try {
|
||||
const result = await apiPostNodeHeroic({ id: node.id });
|
||||
await update({ is_heroic: result.is_heroic });
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
}
|
||||
}, [node.id, update]);
|
||||
|
||||
const onLock = useCallback(async () => {
|
||||
try {
|
||||
const result = await apiLockNode({ id: node.id, is_locked: !node.deleted_at });
|
||||
await update({ deleted_at: result.deleted_at });
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
}
|
||||
}, [node.deleted_at, node.id, update]);
|
||||
|
||||
return { onEdit, onLike, onStar, onLock };
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue