diff --git a/src/components/node/NodePanelInner/styles.scss b/src/components/node/NodePanelInner/styles.scss index f79e771a..58358e10 100644 --- a/src/components/node/NodePanelInner/styles.scss +++ b/src/components/node/NodePanelInner/styles.scss @@ -135,7 +135,7 @@ } 60% { - transform: scale(1.25); + transform: scale(1.4); } 75% { @@ -143,7 +143,7 @@ } 90% { - transform: scale(1.25); + transform: scale(1.4); } 100% { @@ -155,6 +155,12 @@ transition: fill, stroke 0.25s; will-change: transform; + &:global(.is_liked) { + svg { + fill: $red; + } + } + &:hover { fill: $red; animation: pulse 0.75s infinite; diff --git a/src/constants/api.ts b/src/constants/api.ts index 21637064..9fcc0800 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -14,5 +14,6 @@ export const API = { COMMENT: (id: INode['id']) => `/node/${id}/comment`, UPDATE_TAGS: (id: INode['id']) => `/node/${id}/tags`, + POST_LIKE: (id: INode['id']) => `/node/${id}/like`, }, }; diff --git a/src/redux/node/api.ts b/src/redux/node/api.ts index e8f6da65..1ca5da81 100644 --- a/src/redux/node/api.ts +++ b/src/redux/node/api.ts @@ -75,3 +75,14 @@ export const updateNodeTags = ({ .post(API.NODE.UPDATE_TAGS(id), { tags }, configWithToken(access)) .then(resultMiddleware) .catch(errorMiddleware); + +export const postNodeLike = ({ + id, + access, +}: ReturnType & { access: string }): Promise< + IResultWithStatus<{ is_liked: INode['is_liked'] }> +> => + api + .post(API.NODE.POST_LIKE(id), {}, configWithToken(access)) + .then(resultMiddleware) + .catch(errorMiddleware); diff --git a/src/redux/node/sagas.ts b/src/redux/node/sagas.ts index 2749d7fc..854ad486 100644 --- a/src/redux/node/sagas.ts +++ b/src/redux/node/sagas.ts @@ -18,8 +18,16 @@ import { nodeCreate, nodeSetEditor, nodeEdit, + nodeLike, } from './actions'; -import { postNode, getNode, postNodeComment, getNodeComments, updateNodeTags } from './api'; +import { + postNode, + getNode, + postNodeComment, + getNodeComments, + updateNodeTags, + postNodeLike, +} from './api'; import { reqWrapper } from '../auth/sagas'; import { flowSetNodes } from '../flow/actions'; import { ERRORS } from '~/constants/errors'; @@ -29,6 +37,23 @@ import { URLS } from '~/constants/urls'; import { selectNode } from './selectors'; import { IResultWithStatus, INode } from '../types'; import { NODE_EDITOR_DIALOGS, DIALOGS } from '../modal/constants'; +import { INodeState } from './reducer'; +import { IFlowState } from '../flow/reducer'; + +function* updateNodeEverythere(node) { + const { + current: { id }, + }: INodeState = yield select(selectNode); + const flow_nodes: IFlowState['nodes'] = yield select(selectFlowNodes); + + if (id === node.id) { + yield put(nodeSetCurrent(node)); + } + + yield put( + flowSetNodes(flow_nodes.map(flow_node => (flow_node.id === node.id ? node : flow_node))) + ); +} function* onNodeSave({ node }: ReturnType) { yield put(nodeSetSaveErrors({})); @@ -153,6 +178,23 @@ function* onEditSaga({ id }: ReturnType) { yield put(nodeSetEditor(node)); yield put(modalShowDialog(NODE_EDITOR_DIALOGS[node.type])); + + return true; +} + +function* onLikeSaga({ id }: ReturnType) { + const { + current, + current: { is_liked }, + } = yield select(selectNode); + + yield call(updateNodeEverythere, { ...current, is_liked: !is_liked }); + + const { data, error } = yield call(reqWrapper, postNodeLike, { id }); + + if (!error || data.is_liked === !is_liked) return; // ok and matches + + yield call(updateNodeEverythere, { ...current, is_liked }); } export default function* nodeSaga() { @@ -162,4 +204,5 @@ export default function* nodeSaga() { yield takeLatest(NODE_ACTIONS.UPDATE_TAGS, onUpdateTags); yield takeLatest(NODE_ACTIONS.CREATE, onCreateSaga); yield takeLatest(NODE_ACTIONS.EDIT, onEditSaga); + yield takeLatest(NODE_ACTIONS.LIKE, onLikeSaga); }