, 'variant' | 'size' | 'className'>;
+
+const BlockButton = ({
+ variant = ButtonVariant.Secondary,
+ size = ButtonSize.Small,
+ feedId,
+ entityId,
+ entityName,
+ entityType,
+ status,
+ ...attrs
+}: BlockButtonProps): ReactElement => {
+ const { block, unblock } = useContentPreference();
+
+ return (
+
+ );
+};
+
+export default BlockButton;
diff --git a/packages/shared/src/components/contentPreference/FollowButton.tsx b/packages/shared/src/components/contentPreference/FollowButton.tsx
index ba47a27b00..13d401f702 100644
--- a/packages/shared/src/components/contentPreference/FollowButton.tsx
+++ b/packages/shared/src/components/contentPreference/FollowButton.tsx
@@ -95,10 +95,15 @@ export const FollowButton = ({
return null;
}
+ const isFollowing = [
+ ContentPreferenceStatus.Subscribed,
+ ContentPreferenceStatus.Follow,
+ ].includes(currentStatus);
+
return (
{
@@ -108,7 +113,7 @@ export const FollowButton = ({
}}
copyType={copyType}
/>
- {!!currentStatus && showSubscribe && (
+ {isFollowing && showSubscribe && (
);
};
diff --git a/packages/shared/src/components/feeds/FeedSettings/components/BlockedTagList.tsx b/packages/shared/src/components/feeds/FeedSettings/components/BlockedTagList.tsx
index 15faf8f3f1..f7ea7c51cc 100644
--- a/packages/shared/src/components/feeds/FeedSettings/components/BlockedTagList.tsx
+++ b/packages/shared/src/components/feeds/FeedSettings/components/BlockedTagList.tsx
@@ -51,6 +51,8 @@ export const BlockedTagList = ({
canFetchMore: checkFetchMore(queryResult),
fetchNextPage,
}}
+ showBlock
+ showFollow={false}
/>
);
};
diff --git a/packages/shared/src/components/feeds/FeedSettings/components/BlockedUserList.tsx b/packages/shared/src/components/feeds/FeedSettings/components/BlockedUserList.tsx
index d60ce7ed72..ebf3b65a37 100644
--- a/packages/shared/src/components/feeds/FeedSettings/components/BlockedUserList.tsx
+++ b/packages/shared/src/components/feeds/FeedSettings/components/BlockedUserList.tsx
@@ -7,7 +7,7 @@ import { checkFetchMore } from '../../../containers/InfiniteScrolling';
import { Origin } from '../../../../lib/log';
import { CopyType } from '../../../sources/SourceActions/SourceActionsFollow';
import { useBlockedQuery } from '../../../../hooks/contentPreference/useBlockedQuery';
-import { anchorDefaultRel } from '../../../../lib/strings';
+import BlockButton from '../../../contentPreference/BlockButton';
type BlockedUserListProps = {
searchQuery?: string;
@@ -52,14 +52,22 @@ export const BlockedUserList = ({
canFetchMore: checkFetchMore(queryResult),
fetchNextPage,
}}
+ additionalContent={(user) => (
+
+ )}
userInfoProps={{
origin: Origin.BlockedFilter,
- showFollow: true,
+ showFollow: false,
showSubscribe: false,
copyType: CopyType.Custom,
feedId: feed.id,
- rel: anchorDefaultRel,
- target: '_blank',
}}
/>
);
diff --git a/packages/shared/src/components/modals/common.tsx b/packages/shared/src/components/modals/common.tsx
index f0f6af6364..31bf056ac4 100644
--- a/packages/shared/src/components/modals/common.tsx
+++ b/packages/shared/src/components/modals/common.tsx
@@ -214,6 +214,13 @@ const AddToCustomFeedModal = dynamic(
),
);
+const ReportUserModal = dynamic(
+ () =>
+ import(
+ /* webpackChunkName: "reportUserModal" */ './report/ReportUserModal'
+ ),
+);
+
export const modals = {
[LazyModal.SquadMember]: SquadMemberModal,
[LazyModal.UpvotedPopup]: UpvotedPopupModal,
@@ -250,6 +257,7 @@ export const modals = {
[LazyModal.ClickbaitShield]: ClickbaitShieldModal,
[LazyModal.MoveBookmark]: MoveBookmarkModal,
[LazyModal.AddToCustomFeed]: AddToCustomFeedModal,
+ [LazyModal.ReportUser]: ReportUserModal,
};
type GetComponentProps = T extends
diff --git a/packages/shared/src/components/modals/common/types.ts b/packages/shared/src/components/modals/common/types.ts
index 00832f1e0b..64a3b9ff5e 100644
--- a/packages/shared/src/components/modals/common/types.ts
+++ b/packages/shared/src/components/modals/common/types.ts
@@ -60,6 +60,7 @@ export enum LazyModal {
ClickbaitShield = 'clickbaitShield',
MoveBookmark = 'moveBookmark',
AddToCustomFeed = 'addToCustomFeed',
+ ReportUser = 'reportUser',
}
export type ModalTabItem = {
diff --git a/packages/shared/src/components/modals/report/ReportUserModal.tsx b/packages/shared/src/components/modals/report/ReportUserModal.tsx
new file mode 100644
index 0000000000..2caa1a5577
--- /dev/null
+++ b/packages/shared/src/components/modals/report/ReportUserModal.tsx
@@ -0,0 +1,122 @@
+import type { ReactElement } from 'react';
+import React, { useState } from 'react';
+import { useMutation } from '@tanstack/react-query';
+import { ReasonSelectionModal } from './ReasonSelectionModal';
+import type { ReportReason } from '../../../report';
+import { ReportEntity, SEND_REPORT_MUTATION } from '../../../report';
+import { Checkbox } from '../../fields/Checkbox';
+import type { UserShortProfile } from '../../../lib/user';
+import { gqlClient } from '../../../graphql/common';
+import {
+ CONTENT_PREFERENCE_BLOCK_MUTATION,
+ ContentPreferenceType,
+} from '../../../graphql/contentPreference';
+import { useAuthContext } from '../../../contexts/AuthContext';
+import { useToastNotification } from '../../../hooks';
+import { useLazyModal } from '../../../hooks/useLazyModal';
+
+const reportReasons: { value: string; label: string }[] = [
+ { value: 'INAPPROPRIATE', label: 'Inappropriate or NSFW content' },
+ { value: 'TROLLING', label: 'Trolling or disruptive behavior' },
+ { value: 'HARASSMENT', label: 'Harassment or bullying' },
+ { value: 'IMPERSONATION', label: 'Impersonation or false identity' },
+ { value: 'SPAM', label: 'Spam or unsolicited advertising' },
+ { value: 'MISINFORMATION', label: 'Misinformation or false claims' },
+ { value: 'HATEFUL', label: 'Hate speech or discrimination' },
+ { value: 'PRIVACY', label: 'Privacy or copyright violation' },
+ { value: 'PLAGIARISM', label: 'Plagiarism or content theft' },
+ { value: 'OTHER', label: 'Other' },
+];
+
+type ReportUserModalProps = {
+ defaultBlockUser?: boolean;
+ feedId?: string;
+ offendingUser: Pick;
+ onBlockUser?: () => void;
+};
+
+export const ReportUserModal = ({
+ offendingUser,
+ defaultBlockUser,
+ feedId,
+ onBlockUser,
+}: ReportUserModalProps): ReactElement => {
+ const { closeModal: onClose } = useLazyModal();
+ const { displayToast } = useToastNotification();
+ const { user } = useAuthContext();
+ const [blockUser, setBlockUser] = useState(defaultBlockUser);
+ const { isPending: isBlockPending, mutateAsync: blockUserMutation } =
+ useMutation({
+ mutationFn: () =>
+ gqlClient.request(CONTENT_PREFERENCE_BLOCK_MUTATION, {
+ id: offendingUser.id,
+ entity: ContentPreferenceType.User,
+ feedId: feedId ?? user?.id,
+ }),
+ onSuccess: () => {
+ displayToast(`🚫 ${offendingUser.username} has been blocked`);
+ onBlockUser?.();
+ },
+ onError: () => {
+ displayToast(`❌ Failed to block ${offendingUser.username}`);
+ },
+ onSettled: () => onClose(),
+ });
+ const { isPending: isReportPending, mutateAsync: reportUserMutation } =
+ useMutation({
+ mutationFn: ({ reason, text }: { reason: ReportReason; text: string }) =>
+ gqlClient.request(SEND_REPORT_MUTATION, {
+ id: offendingUser.id,
+ type: ReportEntity.User,
+ reason,
+ comment: text,
+ }),
+ onSuccess: () => {
+ if (!blockUser) {
+ displayToast(`🗒️ ${offendingUser.username} has been reported`);
+ }
+ },
+ onError: () => {
+ displayToast(`❌ Failed to report ${offendingUser.username}`);
+ },
+ onSettled: () => onClose(),
+ });
+
+ const onReportUser = (
+ e: React.MouseEvent,
+ reason: ReportReason,
+ text: string,
+ ) => {
+ e.preventDefault();
+ reportUserMutation({ reason, text });
+ if (blockUser) {
+ blockUserMutation();
+ }
+ };
+
+ const isPending = isBlockPending || isReportPending;
+ const checkboxDisabled = defaultBlockUser || isPending;
+ return (
+ setBlockUser(e.target.checked)}
+ checked={blockUser}
+ >
+ Block {offendingUser.username}
+
+ }
+ />
+ );
+};
+
+export default ReportUserModal;
diff --git a/packages/shared/src/components/profile/Header.tsx b/packages/shared/src/components/profile/Header.tsx
index b165f147c5..8c54b48be9 100644
--- a/packages/shared/src/components/profile/Header.tsx
+++ b/packages/shared/src/components/profile/Header.tsx
@@ -3,7 +3,7 @@ import React, { useState } from 'react';
import classNames from 'classnames';
import { useRouter } from 'next/router';
import type { PublicProfile } from '../../lib/user';
-import { SettingsIcon } from '../icons';
+import { BlockIcon, FlagIcon, SettingsIcon } from '../icons';
import { Button, ButtonSize, ButtonVariant } from '../buttons/Button';
import { ProfileImageSize, ProfilePicture } from '../ProfilePicture';
import { largeNumberFormat, ReferralCampaignKey } from '../../lib';
@@ -12,13 +12,19 @@ import { RootPortal } from '../tooltips/Portal';
import { GoBackButton } from '../post/GoBackHeaderMobile';
import { useViewSize, ViewSize } from '../../hooks';
import { FollowButton } from '../contentPreference/FollowButton';
-import { ContentPreferenceType } from '../../graphql/contentPreference';
+import {
+ ContentPreferenceStatus,
+ ContentPreferenceType,
+} from '../../graphql/contentPreference';
import { UpgradeToPlus } from '../UpgradeToPlus';
import { useContentPreferenceStatusQuery } from '../../hooks/contentPreference/useContentPreferenceStatusQuery';
import { usePlusSubscription } from '../../hooks/usePlusSubscription';
import { LogEvent, TargetId } from '../../lib/log';
import CustomFeedOptionsMenu from '../CustomFeedOptionsMenu';
import { useContentPreference } from '../../hooks/contentPreference/useContentPreference';
+import { useLazyModal } from '../../hooks/useLazyModal';
+import { LazyModal } from '../modals/common/types';
+import { MenuIcon } from '../MenuIcon';
export interface HeaderProps {
user: PublicProfile;
@@ -36,6 +42,7 @@ export function Header({
className,
style,
}: HeaderProps): ReactElement {
+ const { openModal } = useLazyModal();
const isMobile = useViewSize(ViewSize.MobileL);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const { isPlus } = usePlusSubscription();
@@ -45,6 +52,26 @@ export function Header({
id: user?.id,
entity: ContentPreferenceType.User,
});
+ const { unblock, block } = useContentPreference();
+
+ const onReportUser = React.useCallback(
+ (defaultBlocked = false) => {
+ openModal({
+ type: LazyModal.ReportUser,
+ props: {
+ offendingUser: {
+ id: user.id,
+ username: user.username,
+ },
+ defaultBlockUser: defaultBlocked,
+ },
+ });
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [user],
+ );
+
+ const blocked = contentPreference?.status === ContentPreferenceStatus.Blocked;
return (
)}
-
+ {!blocked && (
+
+ )}
{!isSameUser && (
@@ -131,6 +160,29 @@ export function Header({
target_id: user.id,
}),
}}
+ additionalOptions={[
+ {
+ icon: ,
+ label: `${blocked ? 'Unblock' : 'Block'} ${user.username}`,
+ action: () =>
+ blocked
+ ? unblock({
+ id: user.id,
+ entity: ContentPreferenceType.User,
+ entityName: user.username,
+ })
+ : block({
+ id: user.id,
+ entity: ContentPreferenceType.User,
+ entityName: user.username,
+ }),
+ },
+ {
+ icon: ,
+ label: 'Report',
+ action: () => onReportUser(),
+ },
+ ]}
/>
)}
diff --git a/packages/shared/src/components/profile/SourceList.tsx b/packages/shared/src/components/profile/SourceList.tsx
index 9420ff49fc..2f7e13a491 100644
--- a/packages/shared/src/components/profile/SourceList.tsx
+++ b/packages/shared/src/components/profile/SourceList.tsx
@@ -20,6 +20,7 @@ import { SourceListPlaceholder } from './SourceListPlaceholder';
import { webappUrl } from '../../lib/constants';
import Link from '../utilities/Link';
import { anchorDefaultRel } from '../../lib/strings';
+import BlockButton from '../contentPreference/BlockButton';
export interface SourceListProps {
scrollingProps: Omit;
@@ -27,6 +28,8 @@ export interface SourceListProps {
placeholderAmount?: number;
isLoading?: boolean;
emptyPlaceholder?: JSX.Element;
+ showFollow?: boolean;
+ showBlock?: boolean;
}
export const SourceList = ({
@@ -35,6 +38,8 @@ export const SourceList = ({
sources,
isLoading,
emptyPlaceholder,
+ showFollow = true,
+ showBlock,
}: SourceListProps): ReactElement => {
const feedSettingsEditContext = useFeedSettingsEditContext();
const feed = feedSettingsEditContext?.feed;
@@ -50,63 +55,78 @@ export const SourceList = ({
placeholder={loader}
>
{sources.map((source) => (
-
-
-
-
-
- {source.name}
-
-
- {source.handle}
- {source.type === SourceType.Squad && (
- <>
-
-
- Squad
-
- >
- )}
-
-
- {source.description}
-
-
+
+
+
+
+ {source.name}
+
+
+ {source.handle}
+ {source.type === SourceType.Squad && (
+ <>
+
+
+ Squad
+
+ >
+ )}
+
+
+ {source.description}
+
+
+ {showBlock && (
+
+ )}
+ {showFollow && (
-
-
+ )}
+
))}
);
diff --git a/packages/shared/src/components/profile/TagList.tsx b/packages/shared/src/components/profile/TagList.tsx
index 4de97d1d76..1b33baac6c 100644
--- a/packages/shared/src/components/profile/TagList.tsx
+++ b/packages/shared/src/components/profile/TagList.tsx
@@ -13,6 +13,7 @@ import { FollowButton } from '../contentPreference/FollowButton';
import { useFeedSettingsEditContext } from '../feeds/FeedSettings/FeedSettingsEditContext';
import { CopyType } from '../sources/SourceActions/SourceActionsFollow';
import { TagListPlaceholder } from './TagListPlaceholder';
+import BlockButton from '../contentPreference/BlockButton';
export interface TagListProps {
scrollingProps: Omit;
@@ -20,6 +21,8 @@ export interface TagListProps {
placeholderAmount?: number;
isLoading?: boolean;
emptyPlaceholder?: JSX.Element;
+ showBlock?: boolean;
+ showFollow?: boolean;
}
export const TagList = ({
@@ -28,6 +31,8 @@ export const TagList = ({
tags,
isLoading,
emptyPlaceholder,
+ showBlock = false,
+ showFollow = true,
}: TagListProps): ReactElement => {
const feedSettingsEditContext = useFeedSettingsEditContext();
const feed = feedSettingsEditContext?.feed;
@@ -51,15 +56,26 @@ export const TagList = ({
>
#{tag.referenceId}
-
+ {showBlock && (
+
+ )}
+ {showFollow && (
+
+ )}
))}
diff --git a/packages/shared/src/components/profile/UserList.tsx b/packages/shared/src/components/profile/UserList.tsx
index 28478226ec..3a3f7df450 100644
--- a/packages/shared/src/components/profile/UserList.tsx
+++ b/packages/shared/src/components/profile/UserList.tsx
@@ -7,6 +7,7 @@ import { UserShortInfo } from './UserShortInfo';
import type { InfiniteScrollingProps } from '../containers/InfiniteScrolling';
import InfiniteScrolling from '../containers/InfiniteScrolling';
import type { UserShortProfile } from '../../lib/user';
+import { anchorDefaultRel } from '../../lib/strings';
export interface UserListProps {
scrollingProps: Omit;
@@ -48,17 +49,28 @@ function UserList({
>
{!!initialItem && initialItem}
{users.map((user, i) => (
-
+
+
+
+
{additionalContent?.(user, i)}
-
+
))}
);
diff --git a/packages/shared/src/components/squads/SquadCommentJoinBanner.tsx b/packages/shared/src/components/squads/SquadCommentJoinBanner.tsx
index 88e5f65a52..1cf4c7956b 100644
--- a/packages/shared/src/components/squads/SquadCommentJoinBanner.tsx
+++ b/packages/shared/src/components/squads/SquadCommentJoinBanner.tsx
@@ -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;
@@ -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: () => {
diff --git a/packages/shared/src/contexts/PaymentContext.tsx b/packages/shared/src/contexts/PaymentContext.tsx
index 72fe61851d..980534b413 100644
--- a/packages/shared/src/contexts/PaymentContext.tsx
+++ b/packages/shared/src/contexts/PaymentContext.tsx
@@ -28,6 +28,7 @@ export type ProductOption = {
label: string;
value: string;
price: string;
+ priceUnformatted: number;
currencyCode: string;
extraLabel: string;
};
@@ -165,6 +166,7 @@ export const PaymentContextProvider = ({
label: item.price.description,
value: item.price.id,
price: item.formattedTotals.total,
+ priceUnformatted: Number(item.totals.total),
currencyCode: productPrices?.data.currencyCode as string,
extraLabel: item.price.customData?.label as string,
})) ?? [],
@@ -182,7 +184,7 @@ export const PaymentContextProvider = ({
}
return monthlyPrices.reduce((acc, plan) => {
- return acc.price < plan.price ? acc : plan;
+ return acc.priceUnformatted < plan.priceUnformatted ? acc : plan;
}).value;
}, [planTypes, productOptions]);
diff --git a/packages/shared/src/graphql/contentPreference.ts b/packages/shared/src/graphql/contentPreference.ts
index f9e20ea33e..ae3f81b3f8 100644
--- a/packages/shared/src/graphql/contentPreference.ts
+++ b/packages/shared/src/graphql/contentPreference.ts
@@ -16,6 +16,7 @@ export enum ContentPreferenceType {
export enum ContentPreferenceStatus {
Follow = 'follow',
Subscribed = 'subscribed',
+ Blocked = 'blocked',
}
type ContentPreferenceUser = Pick<
diff --git a/packages/shared/src/graphql/fragments.ts b/packages/shared/src/graphql/fragments.ts
index 8bc1db5754..5f66d4319b 100644
--- a/packages/shared/src/graphql/fragments.ts
+++ b/packages/shared/src/graphql/fragments.ts
@@ -197,6 +197,9 @@ export const FEED_POST_INFO_FRAGMENT = gql`
image
username
permalink
+ contentPreference {
+ status
+ }
}
type
tags
diff --git a/packages/shared/src/hooks/contentPreference/types.ts b/packages/shared/src/hooks/contentPreference/types.ts
index f7f52c59de..8df23c829e 100644
--- a/packages/shared/src/hooks/contentPreference/types.ts
+++ b/packages/shared/src/hooks/contentPreference/types.ts
@@ -20,6 +20,7 @@ export type ContentPreferenceMutation = ({
feedId?: string;
opts?: Partial<{
extra: Record;
+ hideToast: boolean;
}>;
}) => Promise;
@@ -38,6 +39,7 @@ export const contentPreferenceMutationMatcher: UseMutationMatcher<
RequestKey.ContentPreferenceSubscribe,
RequestKey.ContentPreferenceUnsubscribe,
RequestKey.ContentPreferenceUnblock,
+ RequestKey.ContentPreferenceBlock,
].includes(requestKey as RequestKey)
);
};
@@ -50,6 +52,7 @@ export const mutationKeyToContentPreferenceStatusMap: Partial<
[RequestKey.ContentPreferenceSubscribe]: ContentPreferenceStatus.Subscribed,
[RequestKey.ContentPreferenceUnsubscribe]: ContentPreferenceStatus.Follow,
[RequestKey.ContentPreferenceUnblock]: null,
+ [RequestKey.ContentPreferenceBlock]: ContentPreferenceStatus.Blocked,
};
export const isFollowingContent = (
diff --git a/packages/shared/src/hooks/contentPreference/useContentPreference.ts b/packages/shared/src/hooks/contentPreference/useContentPreference.ts
index ee91c903df..44a9206a43 100644
--- a/packages/shared/src/hooks/contentPreference/useContentPreference.ts
+++ b/packages/shared/src/hooks/contentPreference/useContentPreference.ts
@@ -5,6 +5,7 @@ import {
CONTENT_PREFERENCE_UNBLOCK_MUTATION,
CONTENT_PREFERENCE_UNFOLLOW_MUTATION,
ContentPreferenceStatus,
+ ContentPreferenceType,
} from '../../graphql/contentPreference';
import { useAuthContext } from '../../contexts/AuthContext';
import { gqlClient } from '../../graphql/common';
@@ -220,7 +221,15 @@ export const useContentPreference = ({
feedId,
});
- displayToast(`⛔️ You blocked the following ${entityName}: ${id}`);
+ if (opts?.hideToast) {
+ return;
+ }
+
+ if (entity === ContentPreferenceType.User) {
+ displayToast(`🚫 ${entityName} has been blocked`);
+ } else {
+ displayToast(`⛔️ You blocked the following ${entityName}: ${id}`);
+ }
},
});
diff --git a/packages/shared/src/hooks/notifications/useEnableNotification.ts b/packages/shared/src/hooks/notifications/useEnableNotification.ts
index de3e247547..d995ef7eb6 100644
--- a/packages/shared/src/hooks/notifications/useEnableNotification.ts
+++ b/packages/shared/src/hooks/notifications/useEnableNotification.ts
@@ -51,14 +51,14 @@ export const useEnableNotification = ({
const conditions = [
isLoaded,
!subscribed,
- !isDismissed,
isInitialized,
isPushSupported || isExtension,
];
const shouldShowCta =
- conditions.every(Boolean) ||
- (enabledJustNow && source !== NotificationPromptSource.SquadPostModal);
+ (conditions.every(Boolean) ||
+ (enabledJustNow && source !== NotificationPromptSource.SquadPostModal)) &&
+ !isDismissed;
useEffect(() => {
if (!shouldShowCta) {
diff --git a/packages/shared/src/hooks/usePostById.ts b/packages/shared/src/hooks/usePostById.ts
index fc79efe755..25e760f48b 100644
--- a/packages/shared/src/hooks/usePostById.ts
+++ b/packages/shared/src/hooks/usePostById.ts
@@ -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,
diff --git a/packages/shared/src/report.ts b/packages/shared/src/report.ts
index e9cb1c23ac..2d89c4db00 100644
--- a/packages/shared/src/report.ts
+++ b/packages/shared/src/report.ts
@@ -6,6 +6,7 @@ export enum ReportEntity {
Post = 'post',
Source = 'source',
Comment = 'comment',
+ User = 'user',
}
export enum ReportReason {