Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/pages/Feed/hooks/use-user-permissions.ts
Original file line number Diff line number Diff line change
@@ -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,
};
}
87 changes: 21 additions & 66 deletions src/pages/Feed/partials/ContextMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -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<T extends TAction> {
id: T;
icon: string;
label: string;
}

export default function ContextMenu({
feedItem,
export default function ContextMenu<T extends TAction>({
items,
showSpinner,
onClick,
}: {
feedItem: IFeedItem;
items: IItemData<T>[];
showSpinner?: boolean;
onClick: (action: TAction) => void;
onClick: (action: T) => void;
}) {
const menuButtons = useRef<HTMLDivElement>(null);
const timeout = useRef<NodeJS.Timeout>();
const isShowingMenu = useRef(false);
const { t } = useTranslation();
const [currentUser] = useCurrentUser();
const feed = useFeed();
const isMeAdmin = useIsCurrentUserAdmin();
const timelines = useRef<gsap.core.Timeline[]>([]);

function pauseAllTimelines() {
timelines.current.forEach(timeline => timeline.pause());
timelines.current.forEach((timeline) => timeline.pause());
}

function handleMouseOver(e: React.MouseEvent | React.FocusEvent) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}

Expand All @@ -196,18 +151,18 @@ export default function ContextMenu({
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
>
{buttonsData.map(button => (
{items.map((item) => (
<button
key={button.id}
key={item.id}
onFocus={handleMouseOver}
onBlur={handleMouseOut}
className={styles.button}
onClick={onClick.bind(null, button.id)}
onClick={onClick.bind(null, item.id)}
data-button
>
<i className={`fa fa-${button.icon}`} />
<i className={`fa fa-${button.icon}`} />
<div className={styles.label}>{button.label}</div>
<i className={`fa fa-${item.icon}`} />
<i className={`fa fa-${item.icon}`} />
<div className={styles.label}>{item.label}</div>
</button>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { IFeedItem } from '@feedfarm-shared/types';
Copy link
Owner Author

@dutzi dutzi Jan 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I made two versions of this hook, one being more verbose and explicit (this one) and another (below), which I prefer, because it reads better.

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;
}
42 changes: 42 additions & 0 deletions src/pages/Feed/partials/Post/use-post-context-menu-items.ts
Original file line number Diff line number Diff line change
@@ -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<TAction>[] = [];

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;
}
6 changes: 4 additions & 2 deletions src/pages/Feed/partials/PostMetadata/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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) {
Expand All @@ -39,7 +41,7 @@ export default function PostMetadata({
{isSticky && <i className="fa fa-thumbtack" />}
{!isSticky && (
<ContextMenu
feedItem={item}
items={postContextMenuItems}
onClick={onContextMenuSelect}
showSpinner={showContextMenuSpinner}
/>
Expand Down