mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
added nodes heroize button
This commit is contained in:
parent
c49dbb344d
commit
6b5638b44e
11 changed files with 140 additions and 47 deletions
|
@ -3,67 +3,77 @@ import * as styles from './styles.scss';
|
||||||
import { INode } from '~/redux/types';
|
import { INode } from '~/redux/types';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { NodePanelInner } from '~/components/node/NodePanelInner';
|
import { NodePanelInner } from '~/components/node/NodePanelInner';
|
||||||
|
import pick from 'ramda/es/pick';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: INode;
|
node: Partial<INode>;
|
||||||
layout: {};
|
layout: {};
|
||||||
|
|
||||||
can_edit: boolean;
|
can_edit: boolean;
|
||||||
can_like: boolean;
|
can_like: boolean;
|
||||||
|
can_star: boolean;
|
||||||
|
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
onLike: () => void;
|
onLike: () => void;
|
||||||
|
onStar: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodePanel: FC<IProps> = memo(({ node, layout, can_edit, can_like, onEdit, onLike }) => {
|
const NodePanel: FC<IProps> = memo(
|
||||||
const [stack, setStack] = useState(false);
|
({ node, layout, can_edit, can_like, can_star, onEdit, onLike, onStar }) => {
|
||||||
|
const [stack, setStack] = useState(false);
|
||||||
|
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const getPlace = useCallback(() => {
|
const getPlace = useCallback(() => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
|
|
||||||
const { offsetTop } = ref.current;
|
const { offsetTop } = ref.current;
|
||||||
const { height } = ref.current.getBoundingClientRect();
|
const { height } = ref.current.getBoundingClientRect();
|
||||||
const { scrollY, innerHeight } = window;
|
const { scrollY, innerHeight } = window;
|
||||||
|
|
||||||
setStack(offsetTop > scrollY + innerHeight - height);
|
setStack(offsetTop > scrollY + innerHeight - height);
|
||||||
}, [ref]);
|
}, [ref]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getPlace();
|
getPlace();
|
||||||
window.addEventListener('scroll', getPlace);
|
window.addEventListener('scroll', getPlace);
|
||||||
window.addEventListener('resize', getPlace);
|
window.addEventListener('resize', getPlace);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('scroll', getPlace);
|
window.removeEventListener('scroll', getPlace);
|
||||||
window.removeEventListener('resize', getPlace);
|
window.removeEventListener('resize', getPlace);
|
||||||
};
|
};
|
||||||
}, [layout]);
|
}, [layout]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.place} ref={ref}>
|
<div className={styles.place} ref={ref}>
|
||||||
{stack ? (
|
{stack ? (
|
||||||
createPortal(
|
createPortal(
|
||||||
|
<NodePanelInner
|
||||||
|
node={node}
|
||||||
|
stack
|
||||||
|
onEdit={onEdit}
|
||||||
|
onLike={onLike}
|
||||||
|
onStar={onStar}
|
||||||
|
can_edit={can_edit}
|
||||||
|
can_like={can_like}
|
||||||
|
can_star={can_star}
|
||||||
|
/>,
|
||||||
|
document.body
|
||||||
|
)
|
||||||
|
) : (
|
||||||
<NodePanelInner
|
<NodePanelInner
|
||||||
node={node}
|
node={node}
|
||||||
stack
|
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
onLike={onLike}
|
onLike={onLike}
|
||||||
|
onStar={onStar}
|
||||||
can_edit={can_edit}
|
can_edit={can_edit}
|
||||||
can_like={can_like}
|
can_like={can_like}
|
||||||
/>,
|
can_star={can_star}
|
||||||
document.body
|
/>
|
||||||
)
|
)}
|
||||||
) : (
|
</div>
|
||||||
<NodePanelInner
|
);
|
||||||
node={node}
|
}
|
||||||
onEdit={onEdit}
|
);
|
||||||
onLike={onLike}
|
|
||||||
can_edit={can_edit}
|
|
||||||
can_like={can_like}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export { NodePanel };
|
export { NodePanel };
|
||||||
|
|
|
@ -7,20 +7,24 @@ import { INode } from '~/redux/types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: INode;
|
node: Partial<INode>;
|
||||||
stack?: boolean;
|
stack?: boolean;
|
||||||
|
|
||||||
can_edit: boolean;
|
can_edit: boolean;
|
||||||
can_like: boolean;
|
can_like: boolean;
|
||||||
|
can_star: boolean;
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
onLike: () => void;
|
onLike: () => void;
|
||||||
|
onStar: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodePanelInner: FC<IProps> = ({
|
const NodePanelInner: FC<IProps> = ({
|
||||||
node: { title, user, is_liked },
|
node: { title, user, is_liked, is_heroic },
|
||||||
stack,
|
stack,
|
||||||
|
can_star,
|
||||||
can_edit,
|
can_edit,
|
||||||
can_like,
|
can_like,
|
||||||
|
onStar,
|
||||||
onEdit,
|
onEdit,
|
||||||
onLike,
|
onLike,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -35,6 +39,15 @@ const NodePanelInner: FC<IProps> = ({
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<div className={styles.buttons}>
|
<div className={styles.buttons}>
|
||||||
|
{can_star && (
|
||||||
|
<div className={classNames(styles.star, { is_heroic })}>
|
||||||
|
{is_heroic ? (
|
||||||
|
<Icon icon="star_full" size={24} onClick={onStar} />
|
||||||
|
) : (
|
||||||
|
<Icon icon="star" size={24} onClick={onStar} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{can_edit && (
|
{can_edit && (
|
||||||
<div>
|
<div>
|
||||||
<Icon icon="edit" size={24} onClick={onEdit} />
|
<Icon icon="edit" size={24} onClick={onEdit} />
|
||||||
|
|
|
@ -166,3 +166,18 @@
|
||||||
animation: pulse 0.75s infinite;
|
animation: pulse 0.75s infinite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.star {
|
||||||
|
transition: fill, stroke 0.25s;
|
||||||
|
will-change: transform;
|
||||||
|
|
||||||
|
&:global(.is_heroic) {
|
||||||
|
svg {
|
||||||
|
fill: $orange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
fill: $orange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,5 +15,6 @@ export const API = {
|
||||||
COMMENT: (id: INode['id']) => `/node/${id}/comment`,
|
COMMENT: (id: INode['id']) => `/node/${id}/comment`,
|
||||||
UPDATE_TAGS: (id: INode['id']) => `/node/${id}/tags`,
|
UPDATE_TAGS: (id: INode['id']) => `/node/${id}/tags`,
|
||||||
POST_LIKE: (id: INode['id']) => `/node/${id}/like`,
|
POST_LIKE: (id: INode['id']) => `/node/${id}/like`,
|
||||||
|
POST_STAR: (id: INode['id']) => `/node/${id}/heroic`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC, createElement, useEffect, useCallback, useState, useMemo, memo } from 'react';
|
import React, { FC, createElement, useEffect, useCallback, useState, useMemo, memo } from 'react';
|
||||||
import { RouteComponentProps } from 'react-router';
|
import { RouteComponentProps } from 'react-router';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { canEditNode, canLikeNode } from '~/utils/node';
|
import { canEditNode, canLikeNode, canStarNode } from '~/utils/node';
|
||||||
import { selectNode } from '~/redux/node/selectors';
|
import { selectNode } from '~/redux/node/selectors';
|
||||||
import { Card } from '~/components/containers/Card';
|
import { Card } from '~/components/containers/Card';
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import { NODE_COMPONENTS, NODE_INLINES } from '~/redux/node/constants';
|
||||||
import * as NODE_ACTIONS from '~/redux/node/actions';
|
import * as NODE_ACTIONS from '~/redux/node/actions';
|
||||||
import { CommentForm } from '~/components/node/CommentForm';
|
import { CommentForm } from '~/components/node/CommentForm';
|
||||||
import { selectUser } from '~/redux/auth/selectors';
|
import { selectUser } from '~/redux/auth/selectors';
|
||||||
|
import pick from 'ramda/es/pick';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
node: selectNode(state),
|
node: selectNode(state),
|
||||||
|
@ -29,6 +30,7 @@ const mapDispatchToProps = {
|
||||||
nodeSetCoverImage: NODE_ACTIONS.nodeSetCoverImage,
|
nodeSetCoverImage: NODE_ACTIONS.nodeSetCoverImage,
|
||||||
nodeEdit: NODE_ACTIONS.nodeEdit,
|
nodeEdit: NODE_ACTIONS.nodeEdit,
|
||||||
nodeLike: NODE_ACTIONS.nodeLike,
|
nodeLike: NODE_ACTIONS.nodeLike,
|
||||||
|
nodeStar: NODE_ACTIONS.nodeStar,
|
||||||
};
|
};
|
||||||
|
|
||||||
type IProps = ReturnType<typeof mapStateToProps> &
|
type IProps = ReturnType<typeof mapStateToProps> &
|
||||||
|
@ -40,13 +42,14 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
||||||
match: {
|
match: {
|
||||||
params: { id },
|
params: { id },
|
||||||
},
|
},
|
||||||
node: { is_loading, is_loading_comments, comments = [], current: node, current_cover_image },
|
node: { is_loading, is_loading_comments, comments = [], current: node },
|
||||||
user,
|
user,
|
||||||
user: { is_user },
|
user: { is_user },
|
||||||
nodeLoadNode,
|
nodeLoadNode,
|
||||||
nodeUpdateTags,
|
nodeUpdateTags,
|
||||||
nodeEdit,
|
nodeEdit,
|
||||||
nodeLike,
|
nodeLike,
|
||||||
|
nodeStar,
|
||||||
nodeSetCoverImage,
|
nodeSetCoverImage,
|
||||||
}) => {
|
}) => {
|
||||||
const [layout, setLayout] = useState({});
|
const [layout, setLayout] = useState({});
|
||||||
|
@ -67,12 +70,14 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
||||||
|
|
||||||
const can_edit = useMemo(() => canEditNode(node, user), [node, user]);
|
const can_edit = useMemo(() => canEditNode(node, user), [node, user]);
|
||||||
const can_like = useMemo(() => canLikeNode(node, user), [node, user]);
|
const can_like = useMemo(() => canLikeNode(node, user), [node, user]);
|
||||||
|
const can_star = useMemo(() => canStarNode(node, user), [node, user]);
|
||||||
|
|
||||||
const block = node && node.type && NODE_COMPONENTS[node.type];
|
const block = node && node.type && NODE_COMPONENTS[node.type];
|
||||||
const inline_block = node && node.type && NODE_INLINES[node.type];
|
const inline_block = node && node.type && NODE_INLINES[node.type];
|
||||||
|
|
||||||
const onEdit = useCallback(() => nodeEdit(node.id), [nodeEdit, node]);
|
const onEdit = useCallback(() => nodeEdit(node.id), [nodeEdit, node]);
|
||||||
const onLike = useCallback(() => nodeLike(node.id), [nodeLike, node]);
|
const onLike = useCallback(() => nodeLike(node.id), [nodeLike, node]);
|
||||||
|
const onStar = useCallback(() => nodeStar(node.id), [nodeStar, node]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!node.cover) return;
|
if (!node.cover) return;
|
||||||
|
@ -85,12 +90,14 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
||||||
{block && createElement(block, { node, is_loading, updateLayout, layout })}
|
{block && createElement(block, { node, is_loading, updateLayout, layout })}
|
||||||
|
|
||||||
<NodePanel
|
<NodePanel
|
||||||
node={node}
|
node={pick(['title', 'user', 'is_liked', 'is_heroic'], node)}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
can_edit={can_edit}
|
can_edit={can_edit}
|
||||||
can_like={can_like}
|
can_like={can_like}
|
||||||
|
can_star={can_star}
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
onLike={onLike}
|
onLike={onLike}
|
||||||
|
onStar={onStar}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
|
|
|
@ -80,6 +80,11 @@ export const nodeLike = (id: INode['id']) => ({
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const nodeStar = (id: INode['id']) => ({
|
||||||
|
type: NODE_ACTIONS.STAR,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
export const nodeSetEditor = (editor: INode) => ({
|
export const nodeSetEditor = (editor: INode) => ({
|
||||||
type: NODE_ACTIONS.SET_EDITOR,
|
type: NODE_ACTIONS.SET_EDITOR,
|
||||||
editor,
|
editor,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { api, configWithToken, resultMiddleware, errorMiddleware } from '~/utils/api';
|
import { api, configWithToken, resultMiddleware, errorMiddleware } from '~/utils/api';
|
||||||
import { INode, IResultWithStatus, IComment } from '../types';
|
import { INode, IResultWithStatus, IComment } from '../types';
|
||||||
import { API } from '~/constants/api';
|
import { API } from '~/constants/api';
|
||||||
import { nodeUpdateTags } from './actions';
|
import { nodeUpdateTags, nodeLike, nodeStar } from './actions';
|
||||||
|
|
||||||
export const postNode = ({
|
export const postNode = ({
|
||||||
access,
|
access,
|
||||||
|
@ -79,10 +79,21 @@ export const updateNodeTags = ({
|
||||||
export const postNodeLike = ({
|
export const postNodeLike = ({
|
||||||
id,
|
id,
|
||||||
access,
|
access,
|
||||||
}: ReturnType<typeof nodeUpdateTags> & { access: string }): Promise<
|
}: ReturnType<typeof nodeLike> & { access: string }): Promise<
|
||||||
IResultWithStatus<{ is_liked: INode['is_liked'] }>
|
IResultWithStatus<{ is_liked: INode['is_liked'] }>
|
||||||
> =>
|
> =>
|
||||||
api
|
api
|
||||||
.post(API.NODE.POST_LIKE(id), {}, configWithToken(access))
|
.post(API.NODE.POST_LIKE(id), {}, configWithToken(access))
|
||||||
.then(resultMiddleware)
|
.then(resultMiddleware)
|
||||||
.catch(errorMiddleware);
|
.catch(errorMiddleware);
|
||||||
|
|
||||||
|
export const postNodeStar = ({
|
||||||
|
id,
|
||||||
|
access,
|
||||||
|
}: ReturnType<typeof nodeStar> & { access: string }): Promise<
|
||||||
|
IResultWithStatus<{ is_liked: INode['is_liked'] }>
|
||||||
|
> =>
|
||||||
|
api
|
||||||
|
.post(API.NODE.POST_STAR(id), {}, configWithToken(access))
|
||||||
|
.then(resultMiddleware)
|
||||||
|
.catch(errorMiddleware);
|
||||||
|
|
|
@ -21,6 +21,7 @@ export const NODE_ACTIONS = {
|
||||||
|
|
||||||
EDIT: `${prefix}EDIT`,
|
EDIT: `${prefix}EDIT`,
|
||||||
LIKE: `${prefix}LIKE`,
|
LIKE: `${prefix}LIKE`,
|
||||||
|
STAR: `${prefix}STAR`,
|
||||||
CREATE: `${prefix}CREATE`,
|
CREATE: `${prefix}CREATE`,
|
||||||
|
|
||||||
SET_SAVE_ERRORS: `${prefix}SET_SAVE_ERRORS`,
|
SET_SAVE_ERRORS: `${prefix}SET_SAVE_ERRORS`,
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {
|
||||||
getNodeComments,
|
getNodeComments,
|
||||||
updateNodeTags,
|
updateNodeTags,
|
||||||
postNodeLike,
|
postNodeLike,
|
||||||
|
postNodeStar,
|
||||||
} from './api';
|
} from './api';
|
||||||
import { reqWrapper } from '../auth/sagas';
|
import { reqWrapper } from '../auth/sagas';
|
||||||
import { flowSetNodes } from '../flow/actions';
|
import { flowSetNodes } from '../flow/actions';
|
||||||
|
@ -197,6 +198,21 @@ function* onLikeSaga({ id }: ReturnType<typeof nodeLike>) {
|
||||||
yield call(updateNodeEverythere, { ...current, is_liked });
|
yield call(updateNodeEverythere, { ...current, is_liked });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function* onStarSaga({ id }: ReturnType<typeof nodeLike>) {
|
||||||
|
const {
|
||||||
|
current,
|
||||||
|
current: { is_heroic },
|
||||||
|
} = yield select(selectNode);
|
||||||
|
|
||||||
|
yield call(updateNodeEverythere, { ...current, is_heroic: !is_heroic });
|
||||||
|
|
||||||
|
const { data, error } = yield call(reqWrapper, postNodeStar, { id });
|
||||||
|
|
||||||
|
if (!error || data.is_heroic === !is_heroic) return; // ok and matches
|
||||||
|
|
||||||
|
yield call(updateNodeEverythere, { ...current, is_heroic });
|
||||||
|
}
|
||||||
|
|
||||||
export default function* nodeSaga() {
|
export default function* nodeSaga() {
|
||||||
yield takeLatest(NODE_ACTIONS.SAVE, onNodeSave);
|
yield takeLatest(NODE_ACTIONS.SAVE, onNodeSave);
|
||||||
yield takeLatest(NODE_ACTIONS.LOAD_NODE, onNodeLoad);
|
yield takeLatest(NODE_ACTIONS.LOAD_NODE, onNodeLoad);
|
||||||
|
@ -205,4 +221,5 @@ export default function* nodeSaga() {
|
||||||
yield takeLatest(NODE_ACTIONS.CREATE, onCreateSaga);
|
yield takeLatest(NODE_ACTIONS.CREATE, onCreateSaga);
|
||||||
yield takeLatest(NODE_ACTIONS.EDIT, onEditSaga);
|
yield takeLatest(NODE_ACTIONS.EDIT, onEditSaga);
|
||||||
yield takeLatest(NODE_ACTIONS.LIKE, onLikeSaga);
|
yield takeLatest(NODE_ACTIONS.LIKE, onLikeSaga);
|
||||||
|
yield takeLatest(NODE_ACTIONS.STAR, onStarSaga);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,16 @@ const Sprites: FC<{}> = () => (
|
||||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" />
|
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
|
<g id="star" stroke="none">
|
||||||
|
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||||
|
<path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="star_full" stroke="none">
|
||||||
|
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||||
|
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
|
||||||
|
</g>
|
||||||
|
|
||||||
<g id="heart" stroke="none">
|
<g id="heart" stroke="none">
|
||||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||||
<path d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z" />
|
<path d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z" />
|
||||||
|
|
|
@ -9,3 +9,6 @@ export const canEditNode = (node: Partial<INode>, user: Partial<IUser>): boolean
|
||||||
|
|
||||||
export const canLikeNode = (node: Partial<INode>, user: Partial<IUser>): boolean =>
|
export const canLikeNode = (node: Partial<INode>, user: Partial<IUser>): boolean =>
|
||||||
path(['role'], user) && path(['role'], user) !== USER_ROLES.GUEST;
|
path(['role'], user) && path(['role'], user) !== USER_ROLES.GUEST;
|
||||||
|
|
||||||
|
export const canStarNode = (node: Partial<INode>, user: Partial<IUser>): boolean =>
|
||||||
|
path(['role'], user) && path(['role'], user) === USER_ROLES.ADMIN;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue