Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add block user option on post context menu #4053

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5a17ffe
feat: ReportUserModal
AmarTrebinjac Jan 7, 2025
0540f79
formatting
AmarTrebinjac Jan 8, 2025
8be4262
lint
AmarTrebinjac Jan 8, 2025
eef302c
remove unnecessary check
AmarTrebinjac Jan 8, 2025
63e5977
update conditional
AmarTrebinjac Jan 8, 2025
88066b5
Merge branch 'MI-741' of https://github.com/dailydotdev/apps into MI-…
ilasw Jan 8, 2025
08de8e0
feat: add block user option in post context menu
ilasw Jan 8, 2025
aa74858
remove need to pass onClose
AmarTrebinjac Jan 8, 2025
9b01e41
capitalize values
AmarTrebinjac Jan 8, 2025
aecc124
Merge branch 'MI-741' of https://github.com/dailydotdev/apps into MI-…
ilasw Jan 9, 2025
4388eb2
feat: add optimistic update on unblock
ilasw Jan 9, 2025
02706e2
Merge branch 'MI-655-report-and-block-user' of https://github.com/dai…
ilasw Jan 9, 2025
f9f7bbc
Merge branch 'MI-655-report-and-block-user' of https://github.com/dai…
ilasw Jan 9, 2025
0271bc1
feat: refactor avoiding additional query
ilasw Jan 9, 2025
3d6d522
fix: ssr error
ilasw Jan 9, 2025
b2ac887
Merge branch 'MI-655-report-and-block-user' into MI-717-block-user-on…
ilasw Jan 9, 2025
a8194bf
feat: add contentPreference data for author
ilasw Jan 9, 2025
a190049
Merge branch 'MI-655-report-and-block-user' into MI-717-block-user-on…
ilasw Jan 13, 2025
a4d967d
Merge branch 'MI-717-block-user-on-post-context-menu' of https://gith…
ilasw Jan 14, 2025
e3dd167
Merge branch 'MI-655-report-and-block-user' of https://github.com/dai…
ilasw Jan 14, 2025
7abf0c8
feat: hide follow option for blocked users, change source block label;
ilasw Jan 14, 2025
50466d5
feat: clear cache on block
ilasw Jan 14, 2025
fcb8a0d
Merge branch 'MI-655-report-and-block-user' into MI-717-block-user-on…
ilasw Jan 14, 2025
1fd60e5
feat: block on custom feed feature
ilasw Jan 14, 2025
921f5cf
Merge remote-tracking branch 'origin/MI-717-block-user-on-post-contex…
ilasw Jan 14, 2025
ed004e1
test: update label for block/unblock
ilasw Jan 14, 2025
38898f5
Merge branch 'MI-655-report-and-block-user' into MI-717-block-user-on…
ilasw Jan 16, 2025
adbb414
refactor: invalidate cache is not a utility
ilasw Jan 16, 2025
10cdb3d
feat: added new labels and block directly without report modal
ilasw Jan 16, 2025
63da103
Merge branch 'MI-655-report-and-block-user' into MI-717-block-user-on…
ilasw Jan 16, 2025
8a92e2f
feat: hide post from feed and add undo action to toast
ilasw Jan 16, 2025
7a582eb
Merge remote-tracking branch 'origin/MI-717-block-user-on-post-contex…
ilasw Jan 16, 2025
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
4 changes: 2 additions & 2 deletions packages/shared/src/components/Feed.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ describe('Feed logged in', () => {
);
expect(data).toBeTruthy();
});
const contextBtn = await screen.findByText("Don't show posts from Echo JS");
const contextBtn = await screen.findByText('Block Echo JS');
fireEvent.click(contextBtn);
await waitForNock();
await waitFor(() => expect(mutationCalled).toBeTruthy());
Expand Down Expand Up @@ -673,7 +673,7 @@ describe('Feed logged in', () => {
);
expect(data).toBeTruthy();
});
const contextBtn = await screen.findByText('Show posts from Echo JS');
const contextBtn = await screen.findByText('Unblock Echo JS');

await waitFor(async () => {
fireEvent.click(contextBtn);
Expand Down
123 changes: 96 additions & 27 deletions packages/shared/src/components/PostOptionsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@ import classNames from 'classnames';
import useFeedSettings from '../hooks/useFeedSettings';
import useReportPost from '../hooks/useReportPost';
import type { Post } from '../graphql/posts';
import { UserVote, isVideoPost } from '../graphql/posts';
import { isVideoPost, UserVote } from '../graphql/posts';
import {
TrashIcon,
HammerIcon,
EyeIcon,
AddUserIcon,
BellAddIcon,
BellSubscribedIcon,
BlockIcon,
FlagIcon,
PlusIcon,
EditIcon,
UpvoteIcon,
DownvoteIcon,
SendBackwardIcon,
BringForwardIcon,
PinIcon,
BellSubscribedIcon,
ShareIcon,
DownvoteIcon,
EditIcon,
EyeIcon,
FlagIcon,
FolderIcon,
HammerIcon,
MiniCloseIcon,
MinusIcon,
BellAddIcon,
AddUserIcon,
PinIcon,
PlusIcon,
RemoveUserIcon,
FolderIcon,
SendBackwardIcon,
ShareIcon,
ShieldIcon,
ShieldWarningIcon,
TrashIcon,
UpvoteIcon,
} from './icons';
import type { ReportedCallback } from './modals';
import useTagAndSource from '../hooks/useTagAndSource';
Expand All @@ -50,7 +50,10 @@ import { generateQueryKey } from '../lib/query';
import AuthContext from '../contexts/AuthContext';
import { LogEvent, Origin } from '../lib/log';
import { usePostMenuActions } from '../hooks/usePostMenuActions';
import usePostById, { getPostByIdKey } from '../hooks/usePostById';
import usePostById, {
getPostByIdKey,
invalidatePostCacheById,
} from '../hooks/usePostById';
import { useLazyModal } from '../hooks/useLazyModal';
import { LazyModal } from './modals/common/types';
import { labels } from '../lib';
Expand All @@ -63,7 +66,10 @@ import { useBookmarkReminder } from '../hooks/notifications';
import { BookmarkReminderIcon } from './icons/Bookmark/Reminder';
import { useSourceActionsFollow } from '../hooks/source/useSourceActionsFollow';
import { useContentPreference } from '../hooks/contentPreference/useContentPreference';
import { ContentPreferenceType } from '../graphql/contentPreference';
import {
ContentPreferenceStatus,
ContentPreferenceType,
} from '../graphql/contentPreference';
import { isFollowingContent } from '../hooks/contentPreference/types';
import { useIsSpecialUser } from '../hooks/auth/useIsSpecialUser';
import { useActiveFeedContext } from '../contexts';
Expand Down Expand Up @@ -91,6 +97,26 @@ export interface PostOptionsMenuProps {
allowPin?: boolean;
}

const getBlockLabel = (
name: string,
{ isCustomFeed, isBlocked }: Record<'isCustomFeed' | 'isBlocked', boolean>,
) => {
const blockLabel = {
global: {
block: `Block ${name}`,
unblock: `Unblock ${name}`,
},
feed: {
block: `Remove ${name} from this feed`,
unblock: `Add ${name} to this feed`,
},
};

return blockLabel[isCustomFeed ? 'feed' : 'global'][
isBlocked ? 'unblock' : 'block'
];
};

export default function PostOptionsMenu({
postIndex,
post: initialPost,
Expand Down Expand Up @@ -135,8 +161,7 @@ export default function PostOptionsMenu({
const { logEvent } = useContext(LogContext);
const { hidePost, unhidePost } = useReportPost();
const { openSharePost } = useSharePost(origin);
const { follow, unfollow } = useContentPreference();

const { follow, unfollow, unblock, block } = useContentPreference();
const { openModal } = useLazyModal();

const {
Expand All @@ -156,6 +181,8 @@ export default function PostOptionsMenu({
(excludedSource) => excludedSource.id === post?.source?.id,
);
}, [feedSettings?.excludeSources, post?.source?.id]);
const isBlockedAuthor =
post?.author?.contentPreference?.status === ContentPreferenceStatus.Blocked;

const shouldShowSubscribe =
isLoggedIn &&
Expand Down Expand Up @@ -456,6 +483,7 @@ export default function PostOptionsMenu({
const shouldShowFollow =
!useIsSpecialUser({ userId: post?.author?.id }) &&
post?.author &&
!isBlockedAuthor &&
isLoggedIn;

if (shouldShowFollow) {
Expand Down Expand Up @@ -492,13 +520,54 @@ export default function PostOptionsMenu({
});
}

postOptions.push({
icon: <MenuIcon Icon={BlockIcon} />,
label: isSourceBlocked
? `Show posts from ${post?.source?.name}`
: `Don't show posts from ${post?.source?.name}`,
action: isSourceBlocked ? onUnblockSourceClick : onBlockSourceClick,
});
if (post?.source?.name) {
postOptions.push({
icon: <MenuIcon Icon={BlockIcon} />,
label: getBlockLabel(post.source.name, {
isCustomFeed,
isBlocked: isSourceBlocked,
}),
action: isSourceBlocked ? onUnblockSourceClick : onBlockSourceClick,
});
}

if (post?.author && post?.author?.id !== user?.id) {
postOptions.push({
icon: <MenuIcon Icon={BlockIcon} />,
label: getBlockLabel(post.author.name, {
isCustomFeed,
isBlocked: isBlockedAuthor,
}),
action: async () => {
const params = {
id: post.author.id,
entity: ContentPreferenceType.User,
entityName: post.author.name,
feedId: router.query.slugOrId ? `${router.query.slugOrId}` : null,
};

if (isBlockedAuthor) {
await unblock(params);
} else {
await block({
...params,
opts: {
hideToast: true,
},
});
await showMessageAndRemovePost(
`🚫 ${post.author.name} has been ${
isCustomFeed ? 'removed' : 'blocked'
}`,
postIndex,
() => unblock(params),
);
}

invalidatePostCacheById(client, post.id);
},
});
}

if (video && isVideoPost(post)) {
const isEnabled = checkSettingsEnabledState(video.id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ const reportReasons: { value: string; label: string }[] = [
];

type ReportUserModalProps = {
offendingUser: Pick<UserShortProfile, 'id' | 'username'>;
defaultBlockUser?: boolean;
feedId?: string;
offendingUser: Pick<UserShortProfile, 'id' | 'username'>;
onBlockUser?: () => void;
};

export const ReportUserModal = ({
offendingUser,
defaultBlockUser,
feedId,
onBlockUser,
}: ReportUserModalProps): ReactElement => {
const { closeModal: onClose } = useLazyModal();
const { displayToast } = useToastNotification();
Expand All @@ -47,11 +51,12 @@ export const ReportUserModal = ({
gqlClient.request(CONTENT_PREFERENCE_BLOCK_MUTATION, {
id: offendingUser.id,
entity: ContentPreferenceType.User,
feedId: user?.id,
feedId: feedId ?? user?.id,
}),
onSuccess: () => {
displayToast(`🚫 ${offendingUser.username} has been blocked`);
onClose();
onBlockUser?.();
},
onError: () => {
displayToast(`❌ Failed to block ${offendingUser.username}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import SourceButton from '../cards/common/SourceButton';
import { SQUAD_COMMENT_JOIN_BANNER_KEY } from '../../graphql/squads';
import type { Post } from '../../graphql/posts';
import { ProfileImageSize } from '../ProfilePicture';
import { invalidatePostCacheById } from '../../hooks/usePostById';

export type SquadCommentJoinBannerProps = {
className?: string;
Expand Down Expand Up @@ -44,9 +45,7 @@ export const SquadCommentJoinBanner = ({
displayToast(`🙌 You joined the Squad ${squad.name}`);
setIsSquadMember(true);
if (post?.id) {
queryClient.invalidateQueries({
queryKey: ['post', post.id],
});
invalidatePostCacheById(queryClient, post.id);
}
},
onError: () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/shared/src/graphql/fragments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ export const FEED_POST_INFO_FRAGMENT = gql`
image
username
permalink
contentPreference {
status
}
}
type
tags
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/hooks/contentPreference/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type ContentPreferenceMutation = ({
feedId?: string;
opts?: Partial<{
extra: Record<string, unknown>;
hideToast: boolean;
}>;
}) => Promise<void>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ export const useContentPreference = ({
entity,
feedId,
});

if (opts?.hideToast) {
return;
}

if (entity === ContentPreferenceType.User) {
displayToast(`🚫 ${entityName} has been blocked`);
} else {
Expand Down
11 changes: 11 additions & 0 deletions packages/shared/src/hooks/usePostById.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ export const POST_KEY = 'post';

export const getPostByIdKey = (id: string): QueryKey => [POST_KEY, id];

export const invalidatePostCacheById = (
client: QueryClient,
id: string,
): void => {
const postQueryKey = getPostByIdKey(id);
const postCache = client.getQueryData(postQueryKey);
if (postCache) {
client.invalidateQueries({ queryKey: postQueryKey });
}
};

export const updatePostCache = (
client: QueryClient,
id: string,
Expand Down
Loading