From aeda89d467efc52092447f91bc8375e0641e45d8 Mon Sep 17 00:00:00 2001 From: dutzi Date: Tue, 19 Jan 2021 14:23:44 +0200 Subject: [PATCH] generalize context menu --- src/pages/Feed/hooks/use-user-permissions.ts | 23 +++++ src/pages/Feed/partials/ContextMenu/index.tsx | 87 +++++-------------- .../use-post-context-menu-items-explicit.ts | 58 +++++++++++++ .../Post/use-post-context-menu-items.ts | 42 +++++++++ .../Feed/partials/PostMetadata/index.tsx | 6 +- 5 files changed, 148 insertions(+), 68 deletions(-) create mode 100644 src/pages/Feed/hooks/use-user-permissions.ts create mode 100644 src/pages/Feed/partials/Post/use-post-context-menu-items-explicit.ts create mode 100644 src/pages/Feed/partials/Post/use-post-context-menu-items.ts diff --git a/src/pages/Feed/hooks/use-user-permissions.ts b/src/pages/Feed/hooks/use-user-permissions.ts new file mode 100644 index 0000000..6fc837d --- /dev/null +++ b/src/pages/Feed/hooks/use-user-permissions.ts @@ -0,0 +1,23 @@ +import { IFeedItem } from '@feedfarm-shared/types'; +import useCurrentUser from '../../../hooks/use-current-user'; +import useFeed from '../../../hooks/use-feed'; +import useIsCurrentUserAdmin from '../../../hooks/use-is-current-user-admin'; + +export function useUserPermissions({ feedItem }: { feedItem: IFeedItem }) { + const [currentUser] = useCurrentUser(); + const feed = useFeed(); + const isMeAdmin = useIsCurrentUserAdmin(); + + const isMeAuthor = feedItem.uid === currentUser?.id; + const isMeOwner = feed?.owners.find((user) => user.uid === currentUser?.id); + const isMeModerator = feed?.moderators.find( + (user) => user.uid === currentUser?.id, + ); + + return { + isMeAuthor, + isMeOwner, + isMeModerator, + isMeAdmin, + }; +} diff --git a/src/pages/Feed/partials/ContextMenu/index.tsx b/src/pages/Feed/partials/ContextMenu/index.tsx index 9f8c1f3..bb8fd1b 100644 --- a/src/pages/Feed/partials/ContextMenu/index.tsx +++ b/src/pages/Feed/partials/ContextMenu/index.tsx @@ -1,41 +1,33 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import cx from 'classnames'; import styles from './index.module.scss'; import Spinner from '../../../../components/Spinner'; -import { useTranslation } from 'react-i18next'; import gsap from 'gsap'; -import { IFeedItem } from '@feedfarm-shared/types'; -import useCurrentUser from '../../../../hooks/use-current-user'; -import useFeed from '../../../../hooks/use-feed'; -import useIsCurrentUserAdmin from '../../../../hooks/use-is-current-user-admin'; - -export type TAction = 'delete' | 'ban' | 'edit' | 'view-in-db'; -interface IButtonData { - id: TAction; + +export type TAction = string; + +export interface IItemData { + id: T; icon: string; label: string; } -export default function ContextMenu({ - feedItem, +export default function ContextMenu({ + items, showSpinner, onClick, }: { - feedItem: IFeedItem; + items: IItemData[]; showSpinner?: boolean; - onClick: (action: TAction) => void; + onClick: (action: T) => void; }) { const menuButtons = useRef(null); const timeout = useRef(); const isShowingMenu = useRef(false); - const { t } = useTranslation(); - const [currentUser] = useCurrentUser(); - const feed = useFeed(); - const isMeAdmin = useIsCurrentUserAdmin(); const timelines = useRef([]); function pauseAllTimelines() { - timelines.current.forEach(timeline => timeline.pause()); + timelines.current.forEach((timeline) => timeline.pause()); } function handleMouseOver(e: React.MouseEvent | React.FocusEvent) { @@ -104,7 +96,7 @@ export default function ContextMenu({ timeout.current = setTimeout(hideButtons, 250); } - function hideButtons() { + const hideButtons = useCallback(() => { const buttons = menuButtons.current?.querySelectorAll('button'); if (buttons) { @@ -125,52 +117,15 @@ export default function ContextMenu({ } isShowingMenu.current = false; - } + }, []); useEffect(() => { if (showSpinner) { hideButtons(); } - }, [showSpinner]); - - const buttonsData: IButtonData[] = []; - - const isMeAuthor = feedItem.uid === currentUser?.id; - const isMeOwner = feed?.owners.find(user => user.uid === currentUser?.id); - const isMeModerator = feed?.moderators.find( - user => user.uid === currentUser?.id, - ); - - // if (isMeAdmin || isMeAuthor) { - // buttonsData.push({ - // id: 'edit', - // icon: 'pencil-alt', - // label: t('Edit'), - // }); - // } - if (isMeAuthor || isMeModerator || isMeOwner || isMeAdmin) { - buttonsData.push({ - id: 'delete', - icon: 'trash', - label: t('Delete'), - }); - } - if ((isMeAdmin || isMeOwner || isMeModerator) && !isMeAuthor) { - buttonsData.push({ - id: 'ban', - icon: 'ban', - label: t('Ban User'), - }); - } - if (isMeAdmin) { - buttonsData.push({ - id: 'view-in-db', - icon: 'database', - label: t('View In DB'), - }); - } + }, [hideButtons, showSpinner]); - if (!buttonsData.length) { + if (!items.length) { return null; } @@ -196,18 +151,18 @@ export default function ContextMenu({ onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} > - {buttonsData.map(button => ( + {items.map((item) => ( ))} diff --git a/src/pages/Feed/partials/Post/use-post-context-menu-items-explicit.ts b/src/pages/Feed/partials/Post/use-post-context-menu-items-explicit.ts new file mode 100644 index 0000000..8ed5781 --- /dev/null +++ b/src/pages/Feed/partials/Post/use-post-context-menu-items-explicit.ts @@ -0,0 +1,58 @@ +import { IFeedItem } from '@feedfarm-shared/types'; +import { useTranslation } from 'react-i18next'; +import { useUserPermissions } from '../../hooks/use-user-permissions'; + +export function usePostContextMenuItems({ feedItem }: { feedItem: IFeedItem }) { + const { t } = useTranslation(); + + const { + isMeAuthor, + isMeOwner, + isMeModerator, + isMeAdmin, + } = useUserPermissions({ feedItem }); + + type TAction = 'delete' | 'ban' | 'view-in-db'; + + function isCanDelete() { + return Boolean(isMeAuthor || isMeModerator || isMeOwner || isMeAdmin); + } + + function isCanBan() { + return Boolean((isMeAdmin || isMeOwner || isMeModerator) && !isMeAuthor); + } + + function isCanViewInDb() { + return Boolean(isMeAdmin); + } + + function isShouldShowButton({ id }: { id: TAction }) { + return { + delete: isCanDelete, + ban: isCanBan, + 'view-in-db': isCanViewInDb, + }[id](); + } + + const buttonsData = ([ + { + id: 'delete', + icon: 'trash', + label: t('Delete'), + }, + { + id: 'ban', + icon: 'ban', + label: t('Ban User'), + }, + { + id: 'view-in-db', + icon: 'database', + label: t('View In DB'), + }, + ] as { id: TAction; icon: string; label: string }[]).filter( + isShouldShowButton, + ); + + return buttonsData; +} diff --git a/src/pages/Feed/partials/Post/use-post-context-menu-items.ts b/src/pages/Feed/partials/Post/use-post-context-menu-items.ts new file mode 100644 index 0000000..c4f1e4b --- /dev/null +++ b/src/pages/Feed/partials/Post/use-post-context-menu-items.ts @@ -0,0 +1,42 @@ +import { IFeedItem } from '@feedfarm-shared/types'; +import { IItemData } from '../ContextMenu'; +import { useTranslation } from 'react-i18next'; +import { useUserPermissions } from '../../hooks/use-user-permissions'; + +export function usePostContextMenuItems({ feedItem }: { feedItem: IFeedItem }) { + const { t } = useTranslation(); + + const { + isMeAuthor, + isMeOwner, + isMeModerator, + isMeAdmin, + } = useUserPermissions({ feedItem }); + + type TAction = 'delete' | 'ban' | 'view-in-db'; + const items: IItemData[] = []; + + if (isMeAuthor || isMeModerator || isMeOwner || isMeAdmin) { + items.push({ + id: 'delete', + icon: 'trash', + label: t('Delete'), + }); + } + if ((isMeAdmin || isMeOwner || isMeModerator) && !isMeAuthor) { + items.push({ + id: 'ban', + icon: 'ban', + label: t('Ban User'), + }); + } + if (isMeAdmin) { + items.push({ + id: 'view-in-db', + icon: 'database', + label: t('View In DB'), + }); + } + + return items; +} diff --git a/src/pages/Feed/partials/PostMetadata/index.tsx b/src/pages/Feed/partials/PostMetadata/index.tsx index 20e212a..817b90d 100644 --- a/src/pages/Feed/partials/PostMetadata/index.tsx +++ b/src/pages/Feed/partials/PostMetadata/index.tsx @@ -2,9 +2,10 @@ import React from 'react'; import { TFeedItemType, IFeedItem } from '@feedfarm-shared/types'; import styles from './index.module.scss'; import TimeAgo from '../../../Notifications/partials/TimeAgo'; -import { useTranslation } from 'react-i18next'; import ContextMenu, { TAction } from '../ContextMenu'; import useUserLink from '../../hooks/use-user-link'; +import { usePostContextMenuItems } from '../Post/use-post-context-menu-items'; +import { useTranslation } from 'react-i18next'; export default function PostMetadata({ item, @@ -20,6 +21,7 @@ export default function PostMetadata({ const { t } = useTranslation(); const userLink = useUserLink(item.username, item.uid); + const postContextMenuItems = usePostContextMenuItems({ feedItem: item }); function getPostTypeDescription(type: TFeedItemType) { switch (type) { @@ -39,7 +41,7 @@ export default function PostMetadata({ {isSticky && } {!isSticky && (