From 89f48be355e3c359eccf9b77ed6d71acc85ed040 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Mon, 8 Jul 2024 20:25:15 +0700 Subject: [PATCH 01/35] Add max height to image --- src/components/MediaLoader.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/MediaLoader.tsx b/src/components/MediaLoader.tsx index 862c68a4b..11e8be167 100644 --- a/src/components/MediaLoader.tsx +++ b/src/components/MediaLoader.tsx @@ -97,12 +97,13 @@ export default function MediaLoader({ alt={props.alt || ''} className={cx( commonProps.className, - 'absolute inset-0 m-0 h-full w-full p-0' + 'absolute inset-0 m-0 h-full max-h-96 w-full scale-110 object-cover p-0 blur-lg' )} /> Date: Mon, 8 Jul 2024 22:17:57 +0700 Subject: [PATCH 02/35] Add moderate tabs --- src/modules/chat/HomePage/ChatContent.tsx | 100 +++++++++++++++------- 1 file changed, 71 insertions(+), 29 deletions(-) diff --git a/src/modules/chat/HomePage/ChatContent.tsx b/src/modules/chat/HomePage/ChatContent.tsx index 5cc21ae7c..6f0543dc3 100644 --- a/src/modules/chat/HomePage/ChatContent.tsx +++ b/src/modules/chat/HomePage/ChatContent.tsx @@ -11,6 +11,7 @@ import Meme2EarnIntroModal, { import Modal, { ModalFunctionalityProps } from '@/components/modals/Modal' import { env } from '@/env.mjs' import useIsAddressBlockedInChat from '@/hooks/useIsAddressBlockedInChat' +import useIsModerationAdmin from '@/hooks/useIsModerationAdmin' import useLinkedEvmAddress from '@/hooks/useLinkedEvmAddress' import usePostMemeThreshold from '@/hooks/usePostMemeThreshold' import PointsWidget from '@/modules/points/PointsWidget' @@ -41,7 +42,7 @@ export default function ChatContent({ className }: Props) { 'memes-tab', 'all' ) - if (selectedTab !== 'all' && selectedTab !== 'contest') { + if (!tabStates.includes(selectedTab)) { selectedTab = 'all' } @@ -61,6 +62,10 @@ export default function ChatContent({ className }: Props) { selectedTab === 'contest' && serverTime && env.NEXT_PUBLIC_CONTEST_END_TIME < serverTime + const isCannotPost = + isContestEnded || + selectedTab === 'not-approved' || + selectedTab === 'not-approved-contest' const chatId = selectedTab === 'all' @@ -80,7 +85,7 @@ export default function ChatContent({ className }: Props) { hubId={env.NEXT_PUBLIC_MAIN_SPACE_ID} className='overflow-hidden' customAction={ - isContestEnded ? ( + isCannotPost ? ( <> ) : (
@@ -114,7 +119,13 @@ export default function ChatContent({ className }: Props) { ) } -type TabState = 'all' | 'contest' +const tabStates = [ + 'all', + 'contest', + 'not-approved', + 'not-approved-contest', +] as const +type TabState = (typeof tabStates)[number] function TabButton({ selectedTab, setSelectedTab, @@ -136,7 +147,7 @@ function TabButton({ variant={isSelected ? 'primary' : 'transparent'} className={cx( 'h-10 py-0 text-sm', - size === 'sm' ? 'h-8' : 'h-10', + size === 'sm' ? 'h-8 px-2' : 'h-10', isSelected ? 'bg-background-primary/30' : '', className )} @@ -154,50 +165,81 @@ function Tabs({ selectedTab: TabState setSelectedTab: (tab: TabState) => void }) { + const isAdmin = useIsModerationAdmin() const { data: serverTime, isLoading } = getServerTimeQuery.useQuery(null) const daysLeft = dayjs(env.NEXT_PUBLIC_CONTEST_END_TIME).diff( dayjs(serverTime ?? undefined), 'days' ) + const tabSize: 'sm' | 'md' = isAdmin ? 'sm' : 'md' + return ( -
+
+ {isAdmin && ( + <> + + Pending + + + Pending Contest + + + )} - All memes + {isAdmin ? 'Approved' : 'All memes'} - {env.NEXT_PUBLIC_CONTEST_NAME} - - {(() => { - if (isLoading || !serverTime) return - if (env.NEXT_PUBLIC_CONTEST_END_TIME < serverTime) - return Contest ended - if (daysLeft === 0) { - const hoursLeft = dayjs(env.NEXT_PUBLIC_CONTEST_END_TIME).diff( - dayjs(serverTime ?? undefined), - 'hours' - ) - if (hoursLeft < 1) { - return Less than an hour left - } - return {hoursLeft} hours left - } - return ( - - {daysLeft} day{daysLeft > 1 ? 's' : ''} left - - ) - })()} - + {!isAdmin ? ( + <> + {env.NEXT_PUBLIC_CONTEST_NAME} + + {(() => { + if (isLoading || !serverTime) + return + if (env.NEXT_PUBLIC_CONTEST_END_TIME < serverTime) + return Contest ended + if (daysLeft === 0) { + const hoursLeft = dayjs( + env.NEXT_PUBLIC_CONTEST_END_TIME + ).diff(dayjs(serverTime ?? undefined), 'hours') + if (hoursLeft < 1) { + return Less than an hour left + } + return {hoursLeft} hours left + } + return ( + + {daysLeft} day{daysLeft > 1 ? 's' : ''} left + + ) + })()} + + + ) : ( + Contest + )}
) From f2ddd850f369c5b82e456ce7f82de6d12a61f8ed Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Mon, 8 Jul 2024 22:18:07 +0700 Subject: [PATCH 03/35] Remove unnecessary component --- src/modules/telegram/MemesPage.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/modules/telegram/MemesPage.tsx b/src/modules/telegram/MemesPage.tsx index e60c61d68..f4cd8412c 100644 --- a/src/modules/telegram/MemesPage.tsx +++ b/src/modules/telegram/MemesPage.tsx @@ -9,14 +9,10 @@ const MemesPage = () => { return ( - + ) } -const ChatsContent = () => { - return -} - export default MemesPage From 8d0a33bbdc504dc70e70bac80084e156eaa6f372 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 01:48:32 +0700 Subject: [PATCH 04/35] Finish approve flow --- src/components/Button.tsx | 2 + src/components/chats/ChatItem/ChatItem.tsx | 3 + .../chats/ChatList/ChatItemContainer.tsx | 10 +- .../chats/ChatList/ChatItemWithMenu.tsx | 3 + src/components/chats/ChatList/ChatList.tsx | 7 +- src/components/chats/ChatRoom/ChatRoom.tsx | 3 + .../chats/hooks/usePaginatedMessageIds.ts | 11 +- .../extensions/common/CommonChatItem.tsx | 80 +++++++++-- src/components/extensions/types.ts | 1 + src/modules/chat/HomePage/ChatContent.tsx | 20 ++- src/pages/api/datahub/post.ts | 7 + src/pages/tg/index.tsx | 7 +- src/server/datahub-queue/generated.ts | 23 +++ src/server/datahub-queue/post.ts | 26 ++++ src/services/datahub/generated-query.ts | 8 ++ src/services/datahub/posts/mutation.ts | 21 +++ src/services/datahub/posts/query.ts | 37 +++-- src/services/datahub/posts/subscription.tsx | 10 +- .../subsocial/commentIds/optimistic.ts | 2 +- yarn.lock | 134 +++++++++--------- 20 files changed, 305 insertions(+), 110 deletions(-) diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 777f902df..b7d6d7759 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -20,6 +20,8 @@ export const buttonStyles = cva('relative transition', { muted: 'bg-background-lightest !ring-background-lightest text-text-muted', transparent: 'bg-transparent border border-transparent', redOutline: 'bg-transparent border border-text-red !ring-text-red', + greenOutline: + 'bg-transparent border border-green-500 !ring-green-500 text-green-500', landingPrimary: 'bg-gradient-to-r from-[#DB4646] to-[#F9A11E] text-white hover:!ring-white/50 focus-visible:!ring-white/50', landingPrimaryOutline: diff --git a/src/components/chats/ChatItem/ChatItem.tsx b/src/components/chats/ChatItem/ChatItem.tsx index e98548c09..6a847a883 100644 --- a/src/components/chats/ChatItem/ChatItem.tsx +++ b/src/components/chats/ChatItem/ChatItem.tsx @@ -24,6 +24,7 @@ export type ChatItemProps = Omit, 'children'> & { chatId: string hubId: string bg?: 'background-light' | 'background' + showApproveButton?: boolean } export default function ChatItem({ @@ -35,6 +36,7 @@ export default function ChatItem({ chatId, hubId, bg = 'background-light', + showApproveButton, ...props }: ChatItemProps) { const setReplyTo = useMessageData((state) => state.setReplyTo) @@ -106,6 +108,7 @@ export default function ChatItem({ chatId={chatId} hubId={hubId} bg={bg} + showApproveButton={showApproveButton} /> ) : ( & { containerProps?: ComponentProps<'div'> chatId: string hubId: string + showApproveButton?: boolean } function ChatItemContainer( - { containerProps, chatId, hubId, ...props }: ChatItemContainerProps, + { + containerProps, + chatId, + hubId, + showApproveButton, + ...props + }: ChatItemContainerProps, ref: any ) { const { message } = props @@ -63,6 +70,7 @@ function ChatItemContainer( chatId={chatId} isMyMessage={isMyMessage} hubId={hubId} + showApproveButton={showApproveButton} />
) diff --git a/src/components/chats/ChatList/ChatItemWithMenu.tsx b/src/components/chats/ChatList/ChatItemWithMenu.tsx index b1ec7d685..86b75e371 100644 --- a/src/components/chats/ChatList/ChatItemWithMenu.tsx +++ b/src/components/chats/ChatList/ChatItemWithMenu.tsx @@ -13,6 +13,7 @@ export type ChatItemWithMenuProps = { chatId: string hubId: string scrollToMessage: ScrollToMessage + showApproveButton?: boolean } function InnerChatItemWithMenu({ message, @@ -20,6 +21,7 @@ function InnerChatItemWithMenu({ chatId, hubId, scrollToMessage, + showApproveButton, }: ChatItemWithMenuProps) { return message ? (
) diff --git a/src/components/chats/ChatList/ChatList.tsx b/src/components/chats/ChatList/ChatList.tsx index 1da34e94b..9164e89b4 100644 --- a/src/components/chats/ChatList/ChatList.tsx +++ b/src/components/chats/ChatList/ChatList.tsx @@ -28,6 +28,7 @@ export type ChatListProps = ComponentProps<'div'> & { scrollableContainerClassName?: string hubId: string chatId: string + onlyDisplayUnapprovedMessages?: boolean newMessageNoticeClassName?: string topElement?: React.ReactNode } @@ -54,6 +55,7 @@ function ChatListContent({ chatId, scrollContainerRef: _scrollContainerRef, newMessageNoticeClassName, + onlyDisplayUnapprovedMessages, ...props }: ChatListProps) { const sendEvent = useSendEvent() @@ -77,6 +79,7 @@ function ChatListContent({ } = usePaginatedMessageIds({ hubId, chatId, + onlyDisplayUnapprovedMessages, }) const lastFocusedTime = useLastFocusedMessageTime(chatId, messageIds[0] ?? '') @@ -130,7 +133,8 @@ function ChatListContent({ 0 + (postMetadata?.totalCommentsCount ?? 0) > 0 && + !onlyDisplayUnapprovedMessages ? 'Loading messages...' : undefined } @@ -206,6 +210,7 @@ function ChatListContent({ hubId={hubId} message={message} scrollToMessage={scrollToMessage} + showApproveButton={onlyDisplayUnapprovedMessages} /> ) diff --git a/src/components/chats/ChatRoom/ChatRoom.tsx b/src/components/chats/ChatRoom/ChatRoom.tsx index 3c1d0aade..eab37b531 100644 --- a/src/components/chats/ChatRoom/ChatRoom.tsx +++ b/src/components/chats/ChatRoom/ChatRoom.tsx @@ -22,6 +22,7 @@ export type ChatRoomProps = ComponentProps<'div'> & { chatId: string hubId: string topElement?: ReactNode + onlyDisplayUnapprovedMessages?: boolean } export default function ChatRoom({ @@ -32,6 +33,7 @@ export default function ChatRoom({ chatId, hubId, topElement, + onlyDisplayUnapprovedMessages, ...props }: ChatRoomProps) { const replyTo = useMessageData((state) => state.replyTo) @@ -41,6 +43,7 @@ export default function ChatRoom({
{topElement} { - return data?.pages?.map((page) => page.data).flat() || [] + return Array.from( + new Set(data?.pages?.map((page) => page.data).flat() || []) + ) }, [data?.pages]) const filteredPageIds = useFilterBlockedMessageIds( diff --git a/src/components/extensions/common/CommonChatItem.tsx b/src/components/extensions/common/CommonChatItem.tsx index d34d2533d..ac5a6e3e0 100644 --- a/src/components/extensions/common/CommonChatItem.tsx +++ b/src/components/extensions/common/CommonChatItem.tsx @@ -1,6 +1,7 @@ import Button from '@/components/Button' import LinkText from '@/components/LinkText' import { ProfilePreviewModalName } from '@/components/ProfilePreviewModalWrapper' +import Toast from '@/components/Toast' import { useModerateWithSuccessToast } from '@/components/chats/ChatItem/ChatItemMenus' import ChatRelativeTime from '@/components/chats/ChatItem/ChatRelativeTime' import MessageStatusIndicator from '@/components/chats/ChatItem/MessageStatusIndicator' @@ -10,11 +11,14 @@ import SuperLike from '@/components/content-staking/SuperLike' import useAuthorizedForModeration from '@/hooks/useAuthorizedForModeration' import { getSuperLikeCountQuery } from '@/services/datahub/content-staking/query' import { getModerationReasonsQuery } from '@/services/datahub/moderation/query' +import { useApproveUser } from '@/services/datahub/posts/mutation' +import { getProfileQuery } from '@/services/datahub/profiles/query' import { isMessageSent } from '@/services/subsocial/commentIds/optimistic' import { useMyMainAddress } from '@/stores/my-account' import { cx } from '@/utils/class-names' import { getTimeRelativeToNow } from '@/utils/date' import Linkify from 'linkify-react' +import { toast } from 'sonner' import { ExtensionChatItemProps } from '../types' type DerivativesData = { @@ -63,6 +67,7 @@ export default function CommonChatItem({ chatId, hubId, bg = 'background', + showApproveButton, }: CommonChatItemProps) { const myAddress = useMyMainAddress() const { isAuthorized } = useAuthorizedForModeration(chatId) @@ -132,6 +137,10 @@ export default function CommonChatItem({ (myMessageConfig.children === 'bottom' || (myMessageConfig.children === 'middle' && !body)) + if (showApproveButton) { + othersMessage.checkMark = 'top' + } + return (
{isMyMessage && myMessageConfig.checkMark === 'adaptive-inside' && ( @@ -252,7 +261,12 @@ export default function CommonChatItem({ )} {isAuthorized && ( -
+
+ {showApproveButton && ( + + )}
)} {!isMyMessage && othersMessage.children === 'bottom' && childrenElement} @@ -283,14 +300,59 @@ export default function CommonChatItem({ myMessageConfig.children === 'bottom' && childrenElement} - + {showApproveButton ? ( +
+ ) : ( + + )}
) } + +function ApproveButton({ + ownerId, + chatId, +}: { + chatId: string + ownerId: string +}) { + const { data: profile } = getProfileQuery.useQuery(ownerId) + const { mutate } = useApproveUser({ + onSuccess: () => { + toast.custom((t) => ( + + )) + }, + }) + return ( + + ) +} diff --git a/src/components/extensions/types.ts b/src/components/extensions/types.ts index 1ef502baa..6bd52d1bc 100644 --- a/src/components/extensions/types.ts +++ b/src/components/extensions/types.ts @@ -8,6 +8,7 @@ export type ExtensionChatItemProps = { chatId: string hubId: string bg?: 'background' | 'background-light' + showApproveButton?: boolean } export type RepliedMessagePreviewPartProps = { diff --git a/src/modules/chat/HomePage/ChatContent.tsx b/src/modules/chat/HomePage/ChatContent.tsx index 6f0543dc3..ed2d01a4d 100644 --- a/src/modules/chat/HomePage/ChatContent.tsx +++ b/src/modules/chat/HomePage/ChatContent.tsx @@ -36,6 +36,13 @@ type Props = { className?: string } +const chatIdsBasedOnSelectedTab = { + all: env.NEXT_PUBLIC_MAIN_CHAT_ID, + contest: env.NEXT_PUBLIC_CONTEST_CHAT_ID, + 'not-approved': env.NEXT_PUBLIC_MAIN_CHAT_ID, + 'not-approved-contest': env.NEXT_PUBLIC_CONTEST_CHAT_ID, +} + export default function ChatContent({ className }: Props) { const { query } = useRouter() let [selectedTab, setSelectedTab] = useLocalStorage( @@ -67,12 +74,10 @@ export default function ChatContent({ className }: Props) { selectedTab === 'not-approved' || selectedTab === 'not-approved-contest' - const chatId = - selectedTab === 'all' - ? env.NEXT_PUBLIC_MAIN_CHAT_ID - : env.NEXT_PUBLIC_CONTEST_CHAT_ID - - const isContest = selectedTab === 'contest' + const chatId = chatIdsBasedOnSelectedTab[selectedTab] + const isContest = chatId === env.NEXT_PUBLIC_CONTEST_CHAT_ID + const shouldShowUnapproved = + selectedTab === 'not-approved' || selectedTab === 'not-approved-contest' return ( <> @@ -84,6 +89,7 @@ export default function ChatContent({ className }: Props) { chatId={chatId} hubId={env.NEXT_PUBLIC_MAIN_SPACE_ID} className='overflow-hidden' + onlyDisplayUnapprovedMessages={shouldShowUnapproved} customAction={ isCannotPost ? ( <> @@ -147,7 +153,7 @@ function TabButton({ variant={isSelected ? 'primary' : 'transparent'} className={cx( 'h-10 py-0 text-sm', - size === 'sm' ? 'h-8 px-2' : 'h-10', + size === 'sm' ? 'px-2' : 'h-10', isSelected ? 'bg-background-primary/30' : '', className )} diff --git a/src/pages/api/datahub/post.ts b/src/pages/api/datahub/post.ts index f24a1143c..10d59d04c 100644 --- a/src/pages/api/datahub/post.ts +++ b/src/pages/api/datahub/post.ts @@ -5,6 +5,7 @@ import { UpdatePostOptimisticInput, } from '@/server/datahub-queue/generated' import { + approveUser, createPostData, getCanAccountDo, updatePostData, @@ -59,6 +60,10 @@ export type ApiDatahubPostMutationBody = action: 'update-post' payload: UpdatePostOptimisticInput } + | { + action: 'approve-user' + payload: UpdatePostOptimisticInput + } export type ApiDatahubPostResponse = ApiResponse<{ callId?: string }> const POST_handler = handlerWrapper({ @@ -101,6 +106,8 @@ function datahubPostActionMapping(data: ApiDatahubPostMutationBody) { return createPostData(data.payload) case 'update-post': return updatePostData(data.payload) + case 'approve-user': + return approveUser(data.payload) default: throw new Error('Unknown action') } diff --git a/src/pages/tg/index.tsx b/src/pages/tg/index.tsx index a6c641aae..303d47cb8 100644 --- a/src/pages/tg/index.tsx +++ b/src/pages/tg/index.tsx @@ -14,10 +14,13 @@ async function prefetchChatData(client: QueryClient, chatId: string) { const firstPageData = await getPaginatedPostIdsByPostId.fetchFirstPageQuery( client, - chatId, + { postId: chatId, onlyDisplayUnapprovedMessages: false }, 1 ) - getPaginatedPostIdsByPostId.invalidateFirstQuery(client, chatId) + getPaginatedPostIdsByPostId.invalidateFirstQuery(client, { + postId: chatId, + onlyDisplayUnapprovedMessages: false, + }) const ownerIds = firstPageData.data .map((id) => { const post = getPostQuery.getQueryData(client, id) diff --git a/src/server/datahub-queue/generated.ts b/src/server/datahub-queue/generated.ts index 393d49d66..d2b6db69b 100644 --- a/src/server/datahub-queue/generated.ts +++ b/src/server/datahub-queue/generated.ts @@ -194,6 +194,7 @@ export type Mutation = { moderationInitModerator: IngestDataResponseDto; moderationUnblockResource: IngestDataResponseDto; socialProfileAddReferrerId: IngestDataResponseDto; + socialProfileSetActionPermissions: IngestDataResponseDto; updatePostBlockchainSyncStatus: IngestDataResponseDto; updatePostOptimistic: IngestDataResponseDto; updateSpaceOffChain: IngestDataResponseDto; @@ -315,6 +316,11 @@ export type MutationSocialProfileAddReferrerIdArgs = { }; +export type MutationSocialProfileSetActionPermissionsArgs = { + args: SocialProfileAddReferrerIdInput; +}; + + export type MutationUpdatePostBlockchainSyncStatusArgs = { updatePostBlockchainSyncStatusInput: UpdatePostBlockchainSyncStatusInput; }; @@ -437,6 +443,7 @@ export enum SocialCallName { SynthModerationInitModerator = 'synth_moderation_init_moderator', SynthModerationUnblockResource = 'synth_moderation_unblock_resource', SynthSocialProfileAddReferrerId = 'synth_social_profile_add_referrer_id', + SynthSocialProfileSetActionPermissions = 'synth_social_profile_set_action_permissions', SynthUpdatePostTxFailed = 'synth_update_post_tx_failed', SynthUpdatePostTxRetry = 'synth_update_post_tx_retry', UpdatePost = 'update_post', @@ -571,6 +578,13 @@ export type UpdatePostOptimisticMutationVariables = Exact<{ export type UpdatePostOptimisticMutation = { __typename?: 'Mutation', updatePostOptimistic: { __typename?: 'IngestDataResponseDto', processed: boolean, callId?: string | null, message?: string | null } }; +export type ApproveUserMutationVariables = Exact<{ + input: UpdatePostOptimisticInput; +}>; + + +export type ApproveUserMutation = { __typename?: 'Mutation', updatePostOptimistic: { __typename?: 'IngestDataResponseDto', processed: boolean, callId?: string | null, message?: string | null } }; + export type SetReferrerIdMutationVariables = Exact<{ setReferrerIdInput: SocialProfileAddReferrerIdInput; }>; @@ -704,6 +718,15 @@ export const UpdatePostOptimistic = gql` } } `; +export const ApproveUser = gql` + mutation ApproveUser($input: UpdatePostOptimisticInput!) { + updatePostOptimistic(updatePostOptimisticInput: $input) { + processed + callId + message + } +} + `; export const SetReferrerId = gql` mutation SetReferrerId($setReferrerIdInput: SocialProfileAddReferrerIdInput!) { socialProfileAddReferrerId(args: $setReferrerIdInput) { diff --git a/src/server/datahub-queue/post.ts b/src/server/datahub-queue/post.ts index 06dcba39a..d80e7f1ae 100644 --- a/src/server/datahub-queue/post.ts +++ b/src/server/datahub-queue/post.ts @@ -1,5 +1,7 @@ import { gql } from 'graphql-request' import { + ApproveUserMutation, + ApproveUserMutationVariables, CanAccountDoArgsInput, CreatePostOffChainInput, CreatePostOffChainMutation, @@ -83,3 +85,27 @@ export async function updatePostData(input: UpdatePostOptimisticInput) { throwErrorIfNotProcessed(res.updatePostOptimistic, 'Failed to update post') return res.updatePostOptimistic.callId } + +// TODO: change this if the new mutation is created +const APPROVE_USER_MUTATION = gql` + mutation ApproveUser($input: UpdatePostOptimisticInput!) { + updatePostOptimistic(updatePostOptimisticInput: $input) { + processed + callId + message + } + } +` +export async function approveUser(input: UpdatePostOptimisticInput) { + const res = await datahubQueueRequest< + ApproveUserMutation, + ApproveUserMutationVariables + >({ + document: APPROVE_USER_MUTATION, + variables: { + input, + }, + }) + throwErrorIfNotProcessed(res.updatePostOptimistic, 'Failed to approve user') + return res.updatePostOptimistic.callId +} diff --git a/src/services/datahub/generated-query.ts b/src/services/datahub/generated-query.ts index da025dd97..01bdd8b55 100644 --- a/src/services/datahub/generated-query.ts +++ b/src/services/datahub/generated-query.ts @@ -497,6 +497,7 @@ export type FindPostsFilter = { AND?: InputMaybe> OR?: InputMaybe> activeStaking?: InputMaybe + approvedInRootPost?: InputMaybe createdAtTime?: InputMaybe /** Datetime as ISO 8601 string */ createdAtTimeGt?: InputMaybe @@ -575,6 +576,8 @@ export type FindTasksResponseDto = { export type FindTasksWithFilterArgs = { filter: FindTasksFilter offset?: InputMaybe + orderBy?: InputMaybe + orderDirection?: InputMaybe pageSize?: InputMaybe } @@ -597,6 +600,7 @@ export type GamificationTask = { completedAt?: Maybe createdAt?: Maybe id: Scalars['String']['output'] + index: Scalars['Int']['output'] linkedIdentity: LinkedIdentity metadata?: Maybe name: GamificationTaskName @@ -617,6 +621,7 @@ export type GamificationTaskMetadata = { likesNumberToAchieve?: Maybe referralsNumberToAchieve?: Maybe telegramChannelToJoin?: Maybe + twitterChannelToJoin?: Maybe userExternalProvider?: Maybe userExternalProviderId?: Maybe } @@ -966,6 +971,7 @@ export type Post = { activeStaking: Scalars['Boolean']['output'] activeStakingSuperLikes?: Maybe> activeStakingSuperLikesCount?: Maybe + approvedInRootPost: Scalars['Boolean']['output'] /** is off-chain data CID backed up in blockchain */ backupInBlockchain?: Maybe blockchainSyncFailed: Scalars['Boolean']['output'] @@ -1550,6 +1556,7 @@ export enum SocialCallName { SynthModerationInitModerator = 'synth_moderation_init_moderator', SynthModerationUnblockResource = 'synth_moderation_unblock_resource', SynthSocialProfileAddReferrerId = 'synth_social_profile_add_referrer_id', + SynthSocialProfileSetActionPermissions = 'synth_social_profile_set_action_permissions', SynthUpdatePostTxFailed = 'synth_update_post_tx_failed', SynthUpdatePostTxRetry = 'synth_update_post_tx_retry', UpdatePost = 'update_post', @@ -1570,6 +1577,7 @@ export type SocialProfile = { activeStakingTrial: Scalars['Boolean']['output'] activeStakingTrialFinishedAtTime?: Maybe activeStakingTrialStartedAtTime?: Maybe + allowedCreateCommentRootPostIds: Array balances?: Maybe entranceDailyRewardSequences?: Maybe< Array diff --git a/src/services/datahub/posts/mutation.ts b/src/services/datahub/posts/mutation.ts index b4b02bf81..a5961661b 100644 --- a/src/services/datahub/posts/mutation.ts +++ b/src/services/datahub/posts/mutation.ts @@ -14,6 +14,7 @@ import { import { SendMessageParams } from '@/services/subsocial/commentIds/types' import { getCurrentWallet } from '@/services/subsocial/hooks' import { getMyMainAddress } from '@/stores/my-account' +import mutationWrapper from '@/subsocial-query/base' import { ParentPostIdWrapper, ReplyWrapper } from '@/utils/ipfs' import { LocalStorage } from '@/utils/storage' import { TAGS_REGEX } from '@/utils/strings' @@ -22,6 +23,7 @@ import { PostContent } from '@subsocial/api/types' import { CreatePostCallParsedArgs, PostKind, + SocialCallDataArgs, UpdatePostCallParsedArgs, socialCallName, } from '@subsocial/data-hub-sdk' @@ -295,3 +297,22 @@ export function useSendMessage( }, }) } + +type ApproveUserArgs = + SocialCallDataArgs<'synth_social_profile_set_action_permissions'> +async function approveUser(args: ApproveUserArgs) { + const input = await createSignedSocialDataEvent( + socialCallName.synth_social_profile_set_action_permissions, + { ...getCurrentWallet(), args }, + args + ) + + await apiInstance.post( + '/api/datahub/post', + { + action: 'approve-user', + payload: input as any, + } + ) +} +export const useApproveUser = mutationWrapper(approveUser) diff --git a/src/services/datahub/posts/query.ts b/src/services/datahub/posts/query.ts index 454dc42d6..575c1cbf0 100644 --- a/src/services/datahub/posts/query.ts +++ b/src/services/datahub/posts/query.ts @@ -56,15 +56,20 @@ async function getPaginatedPostIdsByRootPostId({ page, postId, client, + onlyDisplayUnapprovedMessages, }: { postId: string page: number client?: QueryClient | null + onlyDisplayUnapprovedMessages: boolean }): Promise { if (!postId || !client) return { data: [], page, hasMore: false, totalData: 0 } - const oldIds = getPaginatedPostIdsByPostId.getFirstPageData(client, postId) + const oldIds = getPaginatedPostIdsByPostId.getFirstPageData(client, { + postId, + onlyDisplayUnapprovedMessages, + }) const firstPageDataLength = oldIds?.length || CHAT_PER_PAGE // only first page that has dynamic content, where its length can increase from: @@ -83,6 +88,7 @@ async function getPaginatedPostIdsByRootPostId({ args: { filter: { rootPostId: postId, + approvedInRootPost: !onlyDisplayUnapprovedMessages, }, orderBy: 'createdAtTime', orderDirection: QueryOrder.Desc, @@ -158,28 +164,29 @@ async function getPaginatedPostIdsByRootPostId({ totalData, } } +type Data = { postId: string; onlyDisplayUnapprovedMessages: boolean } const COMMENT_IDS_QUERY_KEY = 'comments' -const getQueryKey = (postId: string) => [COMMENT_IDS_QUERY_KEY, postId] +const getQueryKey = (data: Data) => [COMMENT_IDS_QUERY_KEY, data] export const getPaginatedPostIdsByPostId = { getQueryKey, - getFirstPageData: (client: QueryClient, postId: string) => { - const cachedData = client?.getQueryData(getQueryKey(postId)) + getFirstPageData: (client: QueryClient, data: Data) => { + const cachedData = client?.getQueryData(getQueryKey(data)) return ((cachedData as any)?.pages?.[0] as PaginatedPostsData | undefined) ?.data }, fetchFirstPageQuery: async ( client: QueryClient | null, - postId: string, + data: Data, page = 1 ) => { const res = await getPaginatedPostIdsByRootPostId({ - postId, + ...data, page, client, }) if (!client) return res - client.setQueryData(getQueryKey(postId), { + client.setQueryData(getQueryKey(data), { pageParams: [1], pages: [res], }) @@ -187,10 +194,10 @@ export const getPaginatedPostIdsByPostId = { }, setQueryFirstPageData: ( client: QueryClient, - postId: string, + data: Data, updater: (oldIds?: string[]) => string[] | undefined | null ) => { - client.setQueryData(getQueryKey(postId), (oldData: any) => { + client.setQueryData(getQueryKey(data), (oldData: any) => { const firstPage = oldData?.pages?.[0] as PaginatedPostsData | undefined const newPages = [...(oldData?.pages ?? [])] const newFirstPageMessageIds = updater(firstPage?.data) @@ -205,23 +212,23 @@ export const getPaginatedPostIdsByPostId = { } }) }, - invalidateFirstQuery: (client: QueryClient, postId: string) => { - client.invalidateQueries(getQueryKey(postId), { + invalidateFirstQuery: (client: QueryClient, data: Data) => { + client.invalidateQueries(getQueryKey(data), { refetchPage: (_, index) => index === 0, }) }, useInfiniteQuery: ( - postId: string, + data: Data, config?: QueryConfig ): UseInfiniteQueryResult => { const client = useQueryClient() return useInfiniteQuery({ ...config, - queryKey: getQueryKey(postId), + queryKey: getQueryKey(data), queryFn: async ({ pageParam = 1, queryKey }) => { const [_, postId] = queryKey const res = await getPaginatedPostIdsByRootPostId({ - postId, + ...data, page: pageParam, client, }) @@ -231,7 +238,7 @@ export const getPaginatedPostIdsByPostId = { client.setQueryData<{ pageParams: number[] pages: PaginatedPostsData[] - }>(getQueryKey(postId), (oldData) => { + }>(getQueryKey(data), (oldData) => { if ( !oldData || !Array.isArray(oldData.pageParams) || diff --git a/src/services/datahub/posts/subscription.tsx b/src/services/datahub/posts/subscription.tsx index 577f148e2..2aa64818e 100644 --- a/src/services/datahub/posts/subscription.tsx +++ b/src/services/datahub/posts/subscription.tsx @@ -36,10 +36,10 @@ export function useDatahubPostSubscriber(subscribedPostId?: string) { unsubRef.current = subscription(queryClient) // invalidate first page so it will refetch after the websocket connection is disconnected previously when the user is not in the tab if (subscribedPostId) { - getPaginatedPostIdsByPostId.invalidateFirstQuery( - queryClient, - subscribedPostId - ) + getPaginatedPostIdsByPostId.invalidateFirstQuery(queryClient, { + postId: subscribedPostId, + onlyDisplayUnapprovedMessages: false, + }) } } else { if ( @@ -178,7 +178,7 @@ async function processMessage( getPaginatedPostIdsByPostId.setQueryFirstPageData( queryClient, - rootPostId, + { postId: rootPostId, onlyDisplayUnapprovedMessages: false }, (oldData) => { if (!oldData) return oldData const oldIdsSet = new Set(oldData) diff --git a/src/services/subsocial/commentIds/optimistic.ts b/src/services/subsocial/commentIds/optimistic.ts index 6f5528348..fb42af2f6 100644 --- a/src/services/subsocial/commentIds/optimistic.ts +++ b/src/services/subsocial/commentIds/optimistic.ts @@ -46,7 +46,7 @@ export function addOptimisticData({ } as unknown as PostData) getPaginatedPostIdsByPostId.setQueryFirstPageData( client, - params.chatId, + { onlyDisplayUnapprovedMessages: false, postId: params.chatId }, (oldData) => { return [newId, ...(oldData ?? [])] } diff --git a/yarn.lock b/yarn.lock index 1496f407d..e50c98ee6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3991,9 +3991,9 @@ integrity sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg== "@neynar/nodejs-sdk@^1.19.3": - version "1.36.1" - resolved "https://registry.yarnpkg.com/@neynar/nodejs-sdk/-/nodejs-sdk-1.36.1.tgz#36744e659bc3fe310846a86d604a54c2292e9224" - integrity sha512-aQ7o9LjRuEK68JK4Z5++QpH0bThZESKK5nCGF0SfnghyvDxx7pIrqeo6vSioxy434iukUvqraEQD5LzOOKl/2Q== + version "1.37.1" + resolved "https://registry.yarnpkg.com/@neynar/nodejs-sdk/-/nodejs-sdk-1.37.1.tgz#b252380d7c3d2fe47eb1805a7be3b4ce5766e427" + integrity sha512-4Sec4nBE0ePXIctPzppb290prSQGVPzcdiA90+/BarAd8W9q4kDqRLJl8GryHY53+vpdBEwEVACB50VdRmFSpw== dependencies: "@openapitools/openapi-generator-cli" "^2.7.0" axios "^1.6.2" @@ -5595,8 +5595,8 @@ integrity sha512-jHMVmIAjkhSzswZEid2xhWtQb7yyux/8Am/i5xBQfDw6azwSPdr1nfXCKaIYs5R/NRcqi543V9roDQQQogTaKQ== "@subsocial/data-hub-sdk@dappforce/subsocial-data-hub-sdk#staging": - version "0.0.86" - resolved "https://codeload.github.com/dappforce/subsocial-data-hub-sdk/tar.gz/c64c800724e51c5b5a84bbaa902eb85e18751c33" + version "0.0.87" + resolved "https://codeload.github.com/dappforce/subsocial-data-hub-sdk/tar.gz/9eddb0d03c5bde8658afb4fa0705c34e0c8d5e20" dependencies: "@neynar/nodejs-sdk" "^1.19.3" "@subsocial/api" "^0.8.13" @@ -5838,74 +5838,74 @@ "@svgr/plugin-jsx" "^6.5.1" "@svgr/plugin-svgo" "^6.5.1" -"@swc/core-darwin-arm64@1.6.7": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.6.7.tgz#e98a0da9635297728a97faf7f4e11c46f8dfbb46" - integrity sha512-sNb+ghP2OhZyUjS7E5Mf3PqSvoXJ5gY6GBaH2qp8WQxx9VL7ozC4HVo6vkeFJBN5cmYqUCLnhrM3HU4W+7yMSA== - -"@swc/core-darwin-x64@1.6.7": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.6.7.tgz#fccd389046a8fe0d8b294f9657b3861046fcd3bb" - integrity sha512-LQwYm/ATYN5fYSYVPMfComPiFo5i8jh75h1ASvNWhXtS+/+k1dq1zXTJWZRuojd5NXgW3bb6mJtJ2evwYIgYbA== - -"@swc/core-linux-arm-gnueabihf@1.6.7": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.6.7.tgz#f384235e5f14870646157017eb06dfbaed0894c0" - integrity sha512-kEDzVhNci38LX3kdY99t68P2CDf+2QFDk5LawVamXH0iN5DRAO/+wjOhxL8KOHa6wQVqKEt5WrhD+Rrvk/34Yw== - -"@swc/core-linux-arm64-gnu@1.6.7": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.6.7.tgz#d2b8c0c6045eecb96bc3f3dfa7fb31b5ab708cdf" - integrity sha512-SyOBUGfl31xLGpIJ/Jd6GKHtkfZyHBXSwFlK7FmPN//MBQLtTBm4ZaWTnWnGo4aRsJwQdXWDKPyqlMBtnIl1nQ== - -"@swc/core-linux-arm64-musl@1.6.7": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.6.7.tgz#6ae2a160ba535b1f4747d35a124f410545092abe" - integrity sha512-1fOAXkDFbRfItEdMZPxT3du1QWYhgToa4YsnqTujjE8EqJW8K27hIcHRIkVuzp7PNhq8nLBg0JpJM4g27EWD7g== - -"@swc/core-linux-x64-gnu@1.6.7": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.6.7.tgz#6ebcf76fa868321c3b079e5c668c137b9b91df49" - integrity sha512-Gp7uCwPsNO5ATxbyvfTyeNCHUGD9oA+xKMm43G1tWCy+l07gLqWMKp7DIr3L3qPD05TfAVo3OuiOn2abpzOFbw== - -"@swc/core-linux-x64-musl@1.6.7": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.6.7.tgz#41531ef3e1c7123d87b7a7a1b984fa2689032621" - integrity sha512-QeruGBZJ15tadqEMQ77ixT/CYGk20MtlS8wmvJiV+Wsb8gPW5LgCjtupzcLLnoQzDG54JGNCeeZ0l/T8NYsOvA== - -"@swc/core-win32-arm64-msvc@1.6.7": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.6.7.tgz#af0b84a54d01bc3aad12acffa98ebb13fc03c3e6" - integrity sha512-ouRqgSnT95lTCiU/6kJRNS5b1o+p8I/V9jxtL21WUj/JOVhsFmBErqQ0MZyCu514noWiR5BIqOrZXR8C1Knx6Q== - -"@swc/core-win32-ia32-msvc@1.6.7": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.6.7.tgz#c454851c05c26f67d2edc399e1cde9d074744ce4" - integrity sha512-eZAP/EmJ0IcfgAx6B4/SpSjq3aT8gr0ooktfMqw/w0/5lnNrbMl2v+2kvxcneNcF7bp8VNcYZnoHlsP+LvmVbA== - -"@swc/core-win32-x64-msvc@1.6.7": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.6.7.tgz#6ee4a3caf3466971e6b5fb2fba4674924507a2de" - integrity sha512-QOdE+7GQg1UQPS6p0KxzJOh/8GLbJ5zI1vqKArCCB0unFqUfKIjYb2TaH0geEBy3w9qtXxe3ZW6hzxtZSS9lDg== +"@swc/core-darwin-arm64@1.6.13": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.6.13.tgz#dba8f8f747ad32fdb58d5b3aec4f740354d32d1b" + integrity sha512-SOF4buAis72K22BGJ3N8y88mLNfxLNprTuJUpzikyMGrvkuBFNcxYtMhmomO0XHsgLDzOJ+hWzcgjRNzjMsUcQ== + +"@swc/core-darwin-x64@1.6.13": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.6.13.tgz#c120207a9ced298f7382ff711bac10f6541c1c82" + integrity sha512-AW8akFSC+tmPE6YQQvK9S2A1B8pjnXEINg+gGgw0KRUUXunvu1/OEOeC5L2Co1wAwhD7bhnaefi06Qi9AiwOag== + +"@swc/core-linux-arm-gnueabihf@1.6.13": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.6.13.tgz#7b15a1fd32c18dfaf76706632cf8d19146df0d5f" + integrity sha512-f4gxxvDXVUm2HLYXRd311mSrmbpQF2MZ4Ja6XCQz1hWAxXdhRl1gpnZ+LH/xIfGSwQChrtLLVrkxdYUCVuIjFg== + +"@swc/core-linux-arm64-gnu@1.6.13": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.6.13.tgz#066b6e3c805110edb98e5125a222e3d866bf8f68" + integrity sha512-Nf/eoW2CbG8s+9JoLtjl9FByBXyQ5cjdBsA4efO7Zw4p+YSuXDgc8HRPC+E2+ns0praDpKNZtLvDtmF2lL+2Gg== + +"@swc/core-linux-arm64-musl@1.6.13": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.6.13.tgz#43a08bc118f117e485e8a9a23d3cb51fe8b4e301" + integrity sha512-2OysYSYtdw79prJYuKIiux/Gj0iaGEbpS2QZWCIY4X9sGoETJ5iMg+lY+YCrIxdkkNYd7OhIbXdYFyGs/w5LDg== + +"@swc/core-linux-x64-gnu@1.6.13": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.6.13.tgz#0f7358c95f566db6ed8a4249a190043497f41323" + integrity sha512-PkR4CZYJNk5hcd2+tMWBpnisnmYsUzazI1O5X7VkIGFcGePTqJ/bWlfUIVVExWxvAI33PQFzLbzmN5scyIUyGQ== + +"@swc/core-linux-x64-musl@1.6.13": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.6.13.tgz#6e11994ccf858edb3e70d2e8d700a5b1907a68fb" + integrity sha512-OdsY7wryTxCKwGQcwW9jwWg3cxaHBkTTHi91+5nm7hFPpmZMz1HivJrWAMwVE7iXFw+M4l6ugB/wCvpYrUAAjA== + +"@swc/core-win32-arm64-msvc@1.6.13": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.6.13.tgz#b9744644f02eb6519b0fe09031080cbf32174fb1" + integrity sha512-ap6uNmYjwk9M/+bFEuWRNl3hq4VqgQ/Lk+ID/F5WGqczNr0L7vEf+pOsRAn0F6EV+o/nyb3ePt8rLhE/wjHpPg== + +"@swc/core-win32-ia32-msvc@1.6.13": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.6.13.tgz#047302065096883f52b90052d93f9c7e63cdc67b" + integrity sha512-IJ8KH4yIUHTnS/U1jwQmtbfQals7zWPG0a9hbEfIr4zI0yKzjd83lmtS09lm2Q24QBWOCFGEEbuZxR4tIlvfzA== + +"@swc/core-win32-x64-msvc@1.6.13": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.6.13.tgz#efd9706c38aa7dc3515acfa823b8ffa9f4a3c1a6" + integrity sha512-f6/sx6LMuEnbuxtiSL/EkR0Y6qUHFw1XVrh6rwzKXptTipUdOY+nXpKoh+1UsBm/r7H0/5DtOdrn3q5ZHbFZjQ== "@swc/core@^1.3.55": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.6.7.tgz#5d113df161fd8ec29ab8837f385240f41315735e" - integrity sha512-BBzORL9qWz5hZqAZ83yn+WNaD54RH5eludjqIOboolFOK/Pw+2l00/H77H4CEBJnzCIBQszsyqtITmrn4evp0g== + version "1.6.13" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.6.13.tgz#a583f614203d2350e6bb7f7c3c9c36c0e6f2a1da" + integrity sha512-eailUYex6fkfaQTev4Oa3mwn0/e3mQU4H8y1WPuImYQESOQDtVrowwUGDSc19evpBbHpKtwM+hw8nLlhIsF+Tw== dependencies: "@swc/counter" "^0.1.3" "@swc/types" "^0.1.9" optionalDependencies: - "@swc/core-darwin-arm64" "1.6.7" - "@swc/core-darwin-x64" "1.6.7" - "@swc/core-linux-arm-gnueabihf" "1.6.7" - "@swc/core-linux-arm64-gnu" "1.6.7" - "@swc/core-linux-arm64-musl" "1.6.7" - "@swc/core-linux-x64-gnu" "1.6.7" - "@swc/core-linux-x64-musl" "1.6.7" - "@swc/core-win32-arm64-msvc" "1.6.7" - "@swc/core-win32-ia32-msvc" "1.6.7" - "@swc/core-win32-x64-msvc" "1.6.7" + "@swc/core-darwin-arm64" "1.6.13" + "@swc/core-darwin-x64" "1.6.13" + "@swc/core-linux-arm-gnueabihf" "1.6.13" + "@swc/core-linux-arm64-gnu" "1.6.13" + "@swc/core-linux-arm64-musl" "1.6.13" + "@swc/core-linux-x64-gnu" "1.6.13" + "@swc/core-linux-x64-musl" "1.6.13" + "@swc/core-win32-arm64-msvc" "1.6.13" + "@swc/core-win32-ia32-msvc" "1.6.13" + "@swc/core-win32-x64-msvc" "1.6.13" "@swc/counter@^0.1.3": version "0.1.3" From 14828ef01cbf7f40f0a34aace8b4c1e765d29441 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 02:56:14 +0700 Subject: [PATCH 05/35] Finish checker for approved in root post id --- src/@types/subsocial.d.ts | 1 + src/components/chats/ChatItem/ChatItem.tsx | 3 +++ .../extensions/common/CommonChatItem.tsx | 16 +------------ src/services/datahub/events/subscription.tsx | 22 +++++++++++++++++ src/services/datahub/generated-query.ts | 11 +++++++++ src/services/datahub/mappers.ts | 1 + src/services/datahub/posts/fetcher.ts | 1 + src/services/datahub/posts/subscription.tsx | 24 ++++++++++++++++--- 8 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/@types/subsocial.d.ts b/src/@types/subsocial.d.ts index e1292cb4e..e29dc63a6 100644 --- a/src/@types/subsocial.d.ts +++ b/src/@types/subsocial.d.ts @@ -101,6 +101,7 @@ declare module '@subsocial/api/types' { blockchainSyncFailed?: boolean dataType?: 'persistent' | 'optimistic' | 'offChain' parentPostId?: string | null + approvedInRootPost?: boolean }, PostContent > & { requestedId?: string } diff --git a/src/components/chats/ChatItem/ChatItem.tsx b/src/components/chats/ChatItem/ChatItem.tsx index 6a847a883..b039906b2 100644 --- a/src/components/chats/ChatItem/ChatItem.tsx +++ b/src/components/chats/ChatItem/ChatItem.tsx @@ -52,6 +52,9 @@ export default function ChatItem({ const canRenderEmbed = useCanRenderEmbed(link ?? '') + if (showApproveButton && message.struct.approvedInRootPost) return null + if (!showApproveButton && !message.struct.approvedInRootPost) return null + if (!body && (!extensions || extensions.length === 0)) return null const isEmojiOnly = shouldRenderEmojiChatItem(body ?? '') diff --git a/src/components/extensions/common/CommonChatItem.tsx b/src/components/extensions/common/CommonChatItem.tsx index ac5a6e3e0..415554356 100644 --- a/src/components/extensions/common/CommonChatItem.tsx +++ b/src/components/extensions/common/CommonChatItem.tsx @@ -1,7 +1,6 @@ import Button from '@/components/Button' import LinkText from '@/components/LinkText' import { ProfilePreviewModalName } from '@/components/ProfilePreviewModalWrapper' -import Toast from '@/components/Toast' import { useModerateWithSuccessToast } from '@/components/chats/ChatItem/ChatItemMenus' import ChatRelativeTime from '@/components/chats/ChatItem/ChatRelativeTime' import MessageStatusIndicator from '@/components/chats/ChatItem/MessageStatusIndicator' @@ -18,7 +17,6 @@ import { useMyMainAddress } from '@/stores/my-account' import { cx } from '@/utils/class-names' import { getTimeRelativeToNow } from '@/utils/date' import Linkify from 'linkify-react' -import { toast } from 'sonner' import { ExtensionChatItemProps } from '../types' type DerivativesData = { @@ -324,19 +322,7 @@ function ApproveButton({ ownerId: string }) { const { data: profile } = getProfileQuery.useQuery(ownerId) - const { mutate } = useApproveUser({ - onSuccess: () => { - toast.custom((t) => ( - - )) - }, - }) + const { mutate } = useApproveUser() return ( + +
+ + + Memes: 14 + +
+
+
+ + {isAuthorized && ( + + )} + +
+ {/* content */} +
+ + + , + document.body + )} + + ) +} + +export default ProfilePostsListModalWrapper From 78cbc196fbb84c092361d2d2369364ff16054e37 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 19:11:57 +0700 Subject: [PATCH 07/35] Improve toast after sending meme --- src/services/datahub/posts/subscription.tsx | 37 ++++++++++++++------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/services/datahub/posts/subscription.tsx b/src/services/datahub/posts/subscription.tsx index ecb2d1571..0e79abbd9 100644 --- a/src/services/datahub/posts/subscription.tsx +++ b/src/services/datahub/posts/subscription.tsx @@ -3,6 +3,7 @@ import { getPostQuery } from '@/services/api/query' import { commentIdsOptimisticEncoder } from '@/services/subsocial/commentIds/optimistic' import { getMyMainAddress } from '@/stores/my-account' import { useSubscriptionState } from '@/stores/subscription' +import { cx } from '@/utils/class-names' import { QueryClient, useQueryClient } from '@tanstack/react-query' import { gql } from 'graphql-request' import { useEffect, useRef } from 'react' @@ -173,18 +174,30 @@ async function processMessage( null ) const myAddress = getMyMainAddress() - if ( - newPost?.struct.ownerId === myAddress && - isCreationEvent && - newPost.struct.approvedInRootPost - ) { - toast.custom((t) => ( - - )) + if (newPost?.struct.ownerId === myAddress && isCreationEvent) { + if (newPost.struct.approvedInRootPost) { + toast.custom((t) => ( + ( + + )} + t={t} + title='Meme Sent!' + description={`${tokenomics.socialActionPrice.createCommentPoints} points have been used. More memes, more fun!`} + /> + )) + } else { + toast.custom((t) => ( + ( + + )} + title='Your meme is under review' + description={`${tokenomics.socialActionPrice.createCommentPoints} points have been used. We got your meme! Hang tight while we give it a quick review.`} + /> + )) + } } } From dfc48a30b7642efed11c13103d32544f3863f967 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 19:18:06 +0700 Subject: [PATCH 08/35] Make checkmark not clickable --- .../chats/ChatItem/MessageStatusIndicator.tsx | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/components/chats/ChatItem/MessageStatusIndicator.tsx b/src/components/chats/ChatItem/MessageStatusIndicator.tsx index c3abba0d6..40610198f 100644 --- a/src/components/chats/ChatItem/MessageStatusIndicator.tsx +++ b/src/components/chats/ChatItem/MessageStatusIndicator.tsx @@ -1,10 +1,7 @@ -import Button from '@/components/Button' import { useSendEvent } from '@/stores/analytics' import { cx } from '@/utils/class-names' import { PostData } from '@subsocial/api/types' -import { SyntheticEvent, useState } from 'react' import { IoCheckmarkDoneOutline, IoCheckmarkOutline } from 'react-icons/io5' -import CheckMarkExplanationModal from './CheckMarkExplanationModal' export type MessageStatus = 'sending' | 'offChain' | 'optimistic' | 'blockchain' @@ -16,23 +13,18 @@ export default function MessageStatusIndicator({ message, }: MessageStatusIndicatorProps) { const sendEvent = useSendEvent() - const [isOpenCheckmarkModal, setIsOpenCheckmarkModal] = useState(false) + // const [isOpenCheckmarkModal, setIsOpenCheckmarkModal] = useState(false) const messageStatus = getMessageStatusById(message) - const onCheckMarkClick = (e: SyntheticEvent) => { - e.stopPropagation() - sendEvent('click check_mark_button', { type: messageStatus }) - setIsOpenCheckmarkModal(true) - } + // const onCheckMarkClick = (e: SyntheticEvent) => { + // e.stopPropagation() + // sendEvent('click check_mark_button', { type: messageStatus }) + // setIsOpenCheckmarkModal(true) + // } return ( - + /> */} + ) } From 4a74d8bc8bb06fdff4850fa2b4ec68d98995d56e Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 19:21:55 +0700 Subject: [PATCH 09/35] Improve position of time in chat with approve button layout --- src/components/extensions/common/CommonChatItem.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/extensions/common/CommonChatItem.tsx b/src/components/extensions/common/CommonChatItem.tsx index 415554356..fb9765372 100644 --- a/src/components/extensions/common/CommonChatItem.tsx +++ b/src/components/extensions/common/CommonChatItem.tsx @@ -145,7 +145,8 @@ export default function CommonChatItem({
{myMessageCheckMarkElement( From 0459064baf939138eeb6f9e7b80a41ec50717c9f Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 9 Jul 2024 17:30:56 +0300 Subject: [PATCH 10/35] Add posts to profile modal --- src/components/ProfilePreviewModalWrapper.tsx | 25 +- src/components/chats/ChatItem/ChatItem.tsx | 8 +- .../chats/ChatItem/ChatItemWithExtension.tsx | 4 +- .../profilePosts/ProfilePostsList.tsx | 168 ++++++++++++ .../ProfileProstsListModal.tsx | 74 +++-- .../ChatItem/variants/DefaultChatItem.tsx | 8 +- .../chats/ChatItem/variants/EmojiChatItem.tsx | 8 +- .../chats/ChatList/ChatItemContainer.tsx | 15 +- .../chats/ChatList/ChatItemWithMenu.tsx | 8 +- .../chats/ChatList/ChatTopNotice.tsx | 8 +- .../chats/hooks/usePaginatedMessageIds.ts | 45 +++ .../extensions/common/CommonChatItem.tsx | 27 +- src/components/extensions/types.ts | 1 + src/services/datahub/posts/queryByAccount.ts | 259 ++++++++++++++++++ 14 files changed, 612 insertions(+), 46 deletions(-) create mode 100644 src/components/chats/ChatItem/profilePosts/ProfilePostsList.tsx rename src/components/chats/ChatItem/{ => profilePosts}/ProfileProstsListModal.tsx (59%) create mode 100644 src/services/datahub/posts/queryByAccount.ts diff --git a/src/components/ProfilePreviewModalWrapper.tsx b/src/components/ProfilePreviewModalWrapper.tsx index 6673d594f..3caf97638 100644 --- a/src/components/ProfilePreviewModalWrapper.tsx +++ b/src/components/ProfilePreviewModalWrapper.tsx @@ -2,7 +2,7 @@ import { cx } from '@/utils/class-names' import { useState } from 'react' import Name, { NameProps } from './Name' import ProfilePreview from './ProfilePreview' -import ProfilePostsListModalWrapper from './chats/ChatItem/ProfileProstsListModal' +import ProfilePostsListModalWrapper from './chats/ChatItem/profilePosts/ProfileProstsListModal' import Modal from './modals/Modal' export type ProfilePreviewModalWrapperProps = { @@ -39,16 +39,31 @@ export default function ProfilePreviewModalWrapper({ export function ProfilePreviewModalName({ messageId, + chatId, + hubId, + enableProfileModal = true, ...props -}: NameProps & { messageId?: string }) { +}: NameProps & { + messageId: string + chatId: string + hubId: string + enableProfileModal?: boolean +}) { return ( - + {(onClick) => ( { - onClick(e) - props.onClick?.(e) + if (enableProfileModal) { + onClick(e) + props.onClick?.(e) + } }} className={cx('cursor-pointer', props.className)} address={props.address} diff --git a/src/components/chats/ChatItem/ChatItem.tsx b/src/components/chats/ChatItem/ChatItem.tsx index e3b5d620c..33a4655dc 100644 --- a/src/components/chats/ChatItem/ChatItem.tsx +++ b/src/components/chats/ChatItem/ChatItem.tsx @@ -6,7 +6,7 @@ import { ScrollToMessage } from '../ChatList/hooks/useScrollToMessage' import ChatItemMenus from './ChatItemMenus' import ChatItemWithExtension from './ChatItemWithExtension' import Embed, { useCanRenderEmbed } from './Embed' -import ProfilePostsListModalWrapper from './ProfileProstsListModal' +import ProfilePostsListModalWrapper from './profilePosts/ProfileProstsListModal' import DefaultChatItem from './variants/DefaultChatItem' import EmojiChatItem, { shouldRenderEmojiChatItem, @@ -18,6 +18,7 @@ export type ChatItemProps = Omit, 'children'> & { messageBubbleId?: string scrollToMessage?: ScrollToMessage enableChatMenu?: boolean + enableProfileModal?: boolean chatId: string hubId: string bg?: 'background-light' | 'background' @@ -32,6 +33,7 @@ export default function ChatItem({ chatId, hubId, bg = 'background-light', + enableProfileModal = true, ...props }: ChatItemProps) { const { ownerId, id: messageId } = message.struct @@ -63,7 +65,7 @@ export default function ChatItem({ > {(onClick) => ( @@ -97,6 +99,7 @@ export default function ChatItem({ isMyMessage={isMyMessage} chatId={chatId} hubId={hubId} + enableProfileModal={enableProfileModal} bg={bg} /> ) : ( @@ -105,6 +108,7 @@ export default function ChatItem({ isMyMessage={isMyMessage} scrollToMessage={scrollToMessage} chatId={chatId} + enableProfileModal={enableProfileModal} hubId={hubId} bg={bg} /> diff --git a/src/components/chats/ChatItem/ChatItemWithExtension.tsx b/src/components/chats/ChatItem/ChatItemWithExtension.tsx index f0afd3c3f..af19fa594 100644 --- a/src/components/chats/ChatItem/ChatItemWithExtension.tsx +++ b/src/components/chats/ChatItem/ChatItemWithExtension.tsx @@ -4,7 +4,9 @@ import { } from '@/components/extensions/config' import { ExtensionChatItemProps } from '@/components/extensions/types' -export default function ChatItemWithExtension(props: ExtensionChatItemProps) { +export default function ChatItemWithExtension( + props: ExtensionChatItemProps & { enableProfileModal?: boolean } +) { const extensionId = props.message.content?.extensions?.[0] .id as MessageExtensionIds diff --git a/src/components/chats/ChatItem/profilePosts/ProfilePostsList.tsx b/src/components/chats/ChatItem/profilePosts/ProfilePostsList.tsx new file mode 100644 index 000000000..df06535be --- /dev/null +++ b/src/components/chats/ChatItem/profilePosts/ProfilePostsList.tsx @@ -0,0 +1,168 @@ +import Container from '@/components/Container' +import Loading from '@/components/Loading' +import ScrollableContainer from '@/components/ScrollableContainer' +import useAuthorizedForModeration from '@/hooks/useAuthorizedForModeration' +import { useConfigContext } from '@/providers/config/ConfigProvider' +import { getPostQuery } from '@/services/api/query' +import { getPostMetadataQuery } from '@/services/datahub/posts/query' +import { useSendEvent } from '@/stores/analytics' +import { useMyAccount, useMyMainAddress } from '@/stores/my-account' +import { cx } from '@/utils/class-names' +import { sendMessageToParentWindow } from '@/utils/window' +import { ComponentProps, Fragment, useEffect, useId, useRef } from 'react' +import InfiniteScroll from 'react-infinite-scroll-component' +import CenterChatNotice from '../../ChatList/CenterChatNotice' +import ChatItemWithMenu from '../../ChatList/ChatItemWithMenu' +import ChatTopNotice from '../../ChatList/ChatTopNotice' +import { usePaginatedMessageIdsByAccount } from '../../hooks/usePaginatedMessageIds' + +export type ChatListProps = ComponentProps<'div'> & { + asContainer?: boolean + scrollContainerRef?: React.RefObject + scrollableContainerClassName?: string + address: string + hubId: string + chatId: string + newMessageNoticeClassName?: string + topElement?: React.ReactNode +} + +export default function ProfilePostsList(props: ChatListProps) { + const isInitialized = useMyAccount((state) => state.isInitialized) + + return ( + + ) +} + +// If using bigger threshold, the scroll will be janky, but if using 0 threshold, it sometimes won't trigger `next` callback +const SCROLL_THRESHOLD = 20 + +function ProfilePostsListContent({ + asContainer, + scrollableContainerClassName, + hubId, + address, + chatId, + scrollContainerRef: _scrollContainerRef, + newMessageNoticeClassName, + ...props +}: ChatListProps) { + const sendEvent = useSendEvent() + const { enableBackButton } = useConfigContext() + const { data: postMetadata } = getPostMetadataQuery.useQuery(chatId) + + const scrollableContainerId = useId() + + const innerScrollContainerRef = useRef(null) + const scrollContainerRef = _scrollContainerRef || innerScrollContainerRef + + const innerRef = useRef(null) + + const { isAuthorized } = useAuthorizedForModeration(chatId) + + const { messageIds, hasMore, loadMore, totalDataCount, currentPage } = + usePaginatedMessageIdsByAccount({ + hubId, + chatId, + account: address, + isModerator: isAuthorized, + }) + + useEffect(() => { + sendMessageToParentWindow('totalMessage', (totalDataCount ?? 0).toString()) + }, [totalDataCount]) + + const myAddress = useMyMainAddress() + const { data: chat } = getPostQuery.useQuery(chatId) + const isMyChat = chat?.struct.ownerId === myAddress + + const Component = asContainer ? Container<'div'> : 'div' + + const renderedMessageQueries = getPostQuery.useQueries(messageIds) + + return ( +
+ {totalDataCount === 0 && ( + 0 + ? 'Loading messages...' + : undefined + } + className='absolute left-1/2 top-1/2 z-10 -translate-x-1/2 -translate-y-1/2' + /> + )} + + +
+ { + loadMore() + sendEvent('load_more_messages', { currentPage }) + }} + className={cx( + 'relative flex w-full flex-col-reverse !overflow-hidden pb-2', + // need to have enough room to open message menu + 'min-h-[400px]' + )} + hasMore={hasMore} + inverse + scrollableTarget={scrollableContainerId} + loader={} + endMessage={ + renderedMessageQueries.length === 0 ? null : ( + + ) + } + scrollThreshold={`${SCROLL_THRESHOLD}px`} + > + {renderedMessageQueries.map(({ data: message }, index) => { + // bottom message is the first element, because the flex direction is reversed + if (!message) return null + + return ( + + + + ) + })} + +
+
+
+
+ ) +} diff --git a/src/components/chats/ChatItem/ProfileProstsListModal.tsx b/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx similarity index 59% rename from src/components/chats/ChatItem/ProfileProstsListModal.tsx rename to src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx index 370b3263d..06c15ceab 100644 --- a/src/components/chats/ChatItem/ProfileProstsListModal.tsx +++ b/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx @@ -1,15 +1,19 @@ import AddressAvatar from '@/components/AddressAvatar' import Button from '@/components/Button' +import Name from '@/components/Name' import useAuthorizedForModeration from '@/hooks/useAuthorizedForModeration' import { getModerationReasonsQuery } from '@/services/datahub/moderation/query' +import { getPaginatedPostIdsByPostIdAndAccount } from '@/services/datahub/posts/queryByAccount' import { useSendEvent } from '@/stores/analytics' import { cx } from '@/utils/class-names' import { Transition } from '@headlessui/react' +import { useQueryClient } from '@tanstack/react-query' import { useState } from 'react' import { createPortal } from 'react-dom' import { HiOutlineChevronLeft } from 'react-icons/hi2' -import Name from '../../Name' -import { useModerateWithSuccessToast } from './ChatItemMenus' +import SkeletonFallback from '../../../SkeletonFallback' +import { useModerateWithSuccessToast } from '../ChatItemMenus' +import ProfilePostsList from './ProfilePostsList' type ProfilePostsListModalProps = { address: string @@ -32,10 +36,16 @@ const ProfilePostsListModalWrapper = ({ const { mutate: moderate } = useModerateWithSuccessToast(messageId, chatId) const sendEvent = useSendEvent() const { isAuthorized } = useAuthorizedForModeration(chatId) + const client = useQueryClient() const { data: reasons } = getModerationReasonsQuery.useQuery(null) const firstReasonId = reasons?.[0].id + const { data, isLoading } = + getPaginatedPostIdsByPostIdAndAccount.useInfiniteQuery(chatId, address) + + const totalPostsCount = data?.pages[0].totalData || 0 + const onBlockUserClick = () => { sendEvent('block_user', { hubId, chatId }) moderate({ @@ -77,29 +87,33 @@ const ProfilePostsListModalWrapper = ({ leaveTo='opacity-0 -translate-y-24 !duration-150' >
-
-
-
- - -
- - - Memes: 14 - -
+
+
+ + +
+ + + Memes: + + {totalPostsCount} + +
@@ -107,15 +121,19 @@ const ProfilePostsListModalWrapper = ({ )}
-
- {/* content */} +
+
diff --git a/src/components/chats/ChatItem/variants/DefaultChatItem.tsx b/src/components/chats/ChatItem/variants/DefaultChatItem.tsx index f9bbaf003..528ab0792 100644 --- a/src/components/chats/ChatItem/variants/DefaultChatItem.tsx +++ b/src/components/chats/ChatItem/variants/DefaultChatItem.tsx @@ -17,7 +17,9 @@ import MessageStatusIndicator from '../MessageStatusIndicator' import RepliedMessagePreview from '../RepliedMessagePreview' import { ChatItemContentProps } from './types' -export type DefaultChatItemProps = ChatItemContentProps +export type DefaultChatItemProps = ChatItemContentProps & { + enableProfileModal?: boolean +} export default function DefaultChatItem({ chatId, @@ -25,6 +27,7 @@ export default function DefaultChatItem({ message, isMyMessage, scrollToMessage, + enableProfileModal = true, bg = 'background', ...props }: DefaultChatItemProps) { @@ -75,6 +78,9 @@ export default function DefaultChatItem({ labelingData={{ chatId }} messageId={messageId} address={ownerId} + chatId={chatId} + hubId={hubId} + enableProfileModal={enableProfileModal} className={cx('text-sm font-medium text-text-secondary')} /> {/* */} diff --git a/src/components/chats/ChatItem/variants/EmojiChatItem.tsx b/src/components/chats/ChatItem/variants/EmojiChatItem.tsx index 090e523b8..f590a2d61 100644 --- a/src/components/chats/ChatItem/variants/EmojiChatItem.tsx +++ b/src/components/chats/ChatItem/variants/EmojiChatItem.tsx @@ -8,7 +8,9 @@ import MessageStatusIndicator from '../MessageStatusIndicator' import RepliedMessagePreview from '../RepliedMessagePreview' import { ChatItemContentProps } from './types' -export type EmojiChatItemProps = ChatItemContentProps +export type EmojiChatItemProps = ChatItemContentProps & { + enableProfileModal?: boolean +} const EMOJI_FONT_SIZE = { min: 32, @@ -28,6 +30,7 @@ export default function EmojiChatItem({ isMyMessage, scrollToMessage, chatId, + enableProfileModal = true, hubId, ...props }: EmojiChatItemProps) { @@ -69,6 +72,9 @@ export default function EmojiChatItem({ labelingData={{ chatId }} messageId={messageId} address={ownerId} + chatId={chatId} + hubId={hubId} + enableProfileModal={enableProfileModal} className={cx('mr-2 text-sm font-medium text-text-secondary')} /> {/* */} diff --git a/src/components/chats/ChatList/ChatItemContainer.tsx b/src/components/chats/ChatList/ChatItemContainer.tsx index 051a6e6ff..8b5961bb9 100644 --- a/src/components/chats/ChatList/ChatItemContainer.tsx +++ b/src/components/chats/ChatList/ChatItemContainer.tsx @@ -11,12 +11,21 @@ import ChatItem, { ChatItemProps } from '../ChatItem' export type ChatItemContainerProps = Omit & { containerProps?: ComponentProps<'div'> + enableProfileModal?: boolean + showBlockedMessage?: boolean chatId: string hubId: string } function ChatItemContainer( - { containerProps, chatId, hubId, ...props }: ChatItemContainerProps, + { + containerProps, + chatId, + hubId, + showBlockedMessage, + enableProfileModal = true, + ...props + }: ChatItemContainerProps, ref: any ) { const { message } = props @@ -37,7 +46,8 @@ function ChatItemContainer( const { body, extensions } = content || {} const myAddress = useMyMainAddress() - if (isMessageBlocked || (!body && !extensions)) return null + if ((isMessageBlocked && !showBlockedMessage) || (!body && !extensions)) + return null const ownerId = message.struct.ownerId const senderAddress = ownerId ?? '' @@ -62,6 +72,7 @@ function ChatItemContainer( {...props} chatId={chatId} isMyMessage={isMyMessage} + enableProfileModal={enableProfileModal} hubId={hubId} />
diff --git a/src/components/chats/ChatList/ChatItemWithMenu.tsx b/src/components/chats/ChatList/ChatItemWithMenu.tsx index b1ec7d685..7841d901e 100644 --- a/src/components/chats/ChatList/ChatItemWithMenu.tsx +++ b/src/components/chats/ChatList/ChatItemWithMenu.tsx @@ -12,13 +12,17 @@ export type ChatItemWithMenuProps = { message: PostData | null | undefined chatId: string hubId: string - scrollToMessage: ScrollToMessage + enableProfileModal?: boolean + showBlockedMessage?: boolean + scrollToMessage?: ScrollToMessage } function InnerChatItemWithMenu({ message, chatItemClassName, chatId, hubId, + enableProfileModal = true, + showBlockedMessage, scrollToMessage, }: ChatItemWithMenuProps) { return message ? ( @@ -45,7 +49,9 @@ function InnerChatItemWithMenu({ hubId={hubId} chatId={chatId} message={message} + showBlockedMessage={showBlockedMessage} messageBubbleId={getMessageElementId(message.id)} + enableProfileModal={enableProfileModal} scrollToMessage={scrollToMessage} />
diff --git a/src/components/chats/ChatList/ChatTopNotice.tsx b/src/components/chats/ChatList/ChatTopNotice.tsx index 797082944..39229b9ca 100644 --- a/src/components/chats/ChatList/ChatTopNotice.tsx +++ b/src/components/chats/ChatList/ChatTopNotice.tsx @@ -1,13 +1,15 @@ import { cx } from '@/utils/class-names' import { ComponentProps } from 'react' -export type ChatTopNoticeProps = ComponentProps<'div'> +export type ChatTopNoticeProps = ComponentProps<'div'> & { + label?: string +} -export default function ChatTopNotice({ ...props }: ChatTopNoticeProps) { +export default function ChatTopNotice({ label, ...props }: ChatTopNoticeProps) { return (
- You have reached the first message in this chat! + {label ? label : 'You have reached the first message in this chat!'}
) diff --git a/src/components/chats/hooks/usePaginatedMessageIds.ts b/src/components/chats/hooks/usePaginatedMessageIds.ts index f4a9bbca3..aac554ccf 100644 --- a/src/components/chats/hooks/usePaginatedMessageIds.ts +++ b/src/components/chats/hooks/usePaginatedMessageIds.ts @@ -3,6 +3,7 @@ import { PaginatedPostsData, getPaginatedPostIdsByPostId, } from '@/services/datahub/posts/query' +import { getPaginatedPostIdsByPostIdAndAccount } from '@/services/datahub/posts/queryByAccount' import { useMemo } from 'react' type PaginatedData = { @@ -56,3 +57,47 @@ export default function usePaginatedMessageIds({ allIds: filteredPageIds, } } + +type PaginatedByAccountConfig = PaginatedConfig & { + account: string + isModerator: boolean +} + +export function usePaginatedMessageIdsByAccount({ + account, + chatId, + hubId, + isModerator, +}: PaginatedByAccountConfig): PaginatedData { + const { data, fetchNextPage, isLoading } = + getPaginatedPostIdsByPostIdAndAccount.useInfiniteQuery(chatId, account) + + const page = data?.pages + let lastPage: PaginatedPostsData | null = null + if (page && page.length > 0) { + const last = page[page.length - 1] + if (last) { + lastPage = last + } + } + + const flattenedIds = useMemo(() => { + return data?.pages?.map((page) => page.data).flat() || [] + }, [data?.pages]) + + const filteredPageIds = useFilterBlockedMessageIds( + hubId, + chatId, + flattenedIds + ) + + return { + currentPage: lastPage?.page ?? 1, + messageIds: isModerator ? flattenedIds : filteredPageIds, + loadMore: fetchNextPage, + totalDataCount: data?.pages?.[0].totalData || 0, + hasMore: lastPage?.hasMore ?? true, + isLoading, + allIds: isModerator ? flattenedIds : filteredPageIds, + } +} diff --git a/src/components/extensions/common/CommonChatItem.tsx b/src/components/extensions/common/CommonChatItem.tsx index d34d2533d..0460b1ae6 100644 --- a/src/components/extensions/common/CommonChatItem.tsx +++ b/src/components/extensions/common/CommonChatItem.tsx @@ -8,6 +8,7 @@ import RepliedMessagePreview from '@/components/chats/ChatItem/RepliedMessagePre import { getRepliedMessageId } from '@/components/chats/utils' import SuperLike from '@/components/content-staking/SuperLike' import useAuthorizedForModeration from '@/hooks/useAuthorizedForModeration' +import useIsMessageBlocked from '@/hooks/useIsMessageBlocked' import { getSuperLikeCountQuery } from '@/services/datahub/content-staking/query' import { getModerationReasonsQuery } from '@/services/datahub/moderation/query' import { isMessageSent } from '@/services/subsocial/commentIds/optimistic' @@ -40,6 +41,7 @@ type CommonChatItemProps = ExtensionChatItemProps & { textColor?: string bg?: 'background' | 'background-light' showSuperLikeWhenZero?: boolean + enableProfileModal?: boolean } const defaultMyMessageConfig: MyMessageConfig = { @@ -59,6 +61,7 @@ export default function CommonChatItem({ textColor, className, isMyMessage: _isMyMessage, + enableProfileModal = true, showSuperLikeWhenZero, chatId, hubId, @@ -81,6 +84,19 @@ export default function CommonChatItem({ const relativeTime = getTimeRelativeToNow(createdAtTime) const isSent = isMessageSent(message.id, dataType) + const isMessageBlockedInCurrentHub = useIsMessageBlocked( + hubId, + message, + chatId + ) + const isMessageBlockedInOriginalHub = useIsMessageBlocked( + message.struct.spaceId ?? '', + message, + chatId + ) + const isMessageBlocked = + isMessageBlockedInCurrentHub || isMessageBlockedInOriginalHub + const childrenElement = typeof children === 'function' ? children({ isMyMessage, relativeTime, isSent }) @@ -186,6 +202,9 @@ export default function CommonChatItem({ messageId={message.id} address={ownerId} color={textColor} + chatId={chatId} + hubId={hubId} + enableProfileModal={enableProfileModal} className={cx('text-sm font-medium text-text-secondary')} /> {/* */} @@ -257,6 +276,7 @@ export default function CommonChatItem({ variant='redOutline' isLoading={loadingModeration} loadingText='Blocking...' + disabled={isMessageBlocked} onClick={(e) => { e.stopPropagation() moderate({ @@ -271,9 +291,12 @@ export default function CommonChatItem({ }) }} size='sm' - className='w-full !text-text-red disabled:!border-text-muted disabled:!text-text-muted disabled:!ring-text-muted' + className={cx('w-full !text-text-red', { + ['!bg-[#EF4444] disabled:border-none disabled:!text-white disabled:!ring-0 disabled:!brightness-100']: + isMessageBlocked, + })} > - Block message + {isMessageBlocked ? 'Blocked' : 'Block message'}
)} diff --git a/src/components/extensions/types.ts b/src/components/extensions/types.ts index 1ef502baa..16bb555a4 100644 --- a/src/components/extensions/types.ts +++ b/src/components/extensions/types.ts @@ -7,6 +7,7 @@ export type ExtensionChatItemProps = { scrollToMessage?: (messageId: string) => Promise chatId: string hubId: string + enableProfileModal?: boolean bg?: 'background' | 'background-light' } diff --git a/src/services/datahub/posts/queryByAccount.ts b/src/services/datahub/posts/queryByAccount.ts new file mode 100644 index 000000000..5add26377 --- /dev/null +++ b/src/services/datahub/posts/queryByAccount.ts @@ -0,0 +1,259 @@ +import { CHAT_PER_PAGE } from '@/constants/chat' +import { getPostQuery } from '@/services/api/query' +import { + commentIdsOptimisticEncoder, + isClientGeneratedOptimisticId, +} from '@/services/subsocial/commentIds/optimistic' +import { PostData } from '@subsocial/api/types' +import { + QueryClient, + QueryClientConfig, + UseInfiniteQueryResult, + useInfiniteQuery, + useQueryClient, +} from '@tanstack/react-query' +import { gql } from 'graphql-request' +import { + GetCommentIdsInPostIdQuery, + GetCommentIdsInPostIdQueryVariables, + QueryOrder, +} from '../generated-query' +import { mapDatahubPostFragment } from '../mappers' +import { datahubQueryRequest } from '../utils' +import { DATAHUB_POST_FRAGMENT } from './fetcher' +import { PaginatedPostsData } from './query' + +const GET_COMMENT_IDS_IN_POST_ID = gql` + ${DATAHUB_POST_FRAGMENT} + query GetCommentIdsInPostId($args: FindPostsWithFilterArgs!) { + posts(args: $args) { + data { + ...DatahubPostFragment + } + total + } + } +` +async function getPaginatedPostIdsByRootPostIdAndAccount({ + page, + postId, + account, + client, +}: { + postId: string + page: number + account: string + client?: QueryClient | null +}): Promise { + if (!postId || !client) + return { data: [], page, hasMore: false, totalData: 0 } + + const oldIds = getPaginatedPostIdsByPostIdAndAccount.getFirstPageData( + client, + postId, + account + )?.data + const firstPageDataLength = oldIds?.length || CHAT_PER_PAGE + + // only first page that has dynamic content, where its length can increase from: + // - subscription + // - invalidation + // so the offset has to accommodate the length of the current first page + let offset = Math.max((page - 2) * CHAT_PER_PAGE + firstPageDataLength, 0) + if (page === 1) offset = 0 + + const res = await datahubQueryRequest< + GetCommentIdsInPostIdQuery, + GetCommentIdsInPostIdQueryVariables + >({ + document: GET_COMMENT_IDS_IN_POST_ID, + variables: { + args: { + filter: { + rootPostId: postId, + createdByAccountAddress: account, + }, + orderBy: 'createdAtTime', + orderDirection: QueryOrder.Desc, + pageSize: CHAT_PER_PAGE, + offset, + }, + }, + }) + const optimisticIds = new Set() + const ids: string[] = [] + const messages: PostData[] = [] + res.posts.data.forEach((post) => { + optimisticIds.add('') + + const id = post.id + ids.push(id) + + const mapped = mapDatahubPostFragment(post) + messages.push(mapped) + getPostQuery.setQueryData(client, id, mapped) + }) + const totalData = res.posts.total ?? 0 + const hasMore = offset + ids.length < totalData + + const idsSet = new Set(ids) + + // only adding the client optimistic ids, and unincluded ids if refetching first page + // for fetching first page, no ids that has been fetched will be removed + // ex: first fetch: id 1-50, and after invalidation, there is new data id 0 + // the result should be id 0-50 instead of 0-49 + let unincludedOptimisticIds: string[] = [] + let unincludedFirstPageIds: string[] = [] + if (page === 1 && oldIds) { + const oldOptimisticIds = [] + + let unincludedIdsIndex = oldIds.length + + // for example, if the page size is 3, and there is 1 new id + // ids: [new, old1, old2], and oldIds: [old1, old2, old3] + // so we need to get the unincludedOldIds from the end of the array (old3) + let hasFoundIncludedId = false + + for (let i = oldIds.length - 1; i >= 0; i--) { + const id = oldIds[i] + + if (isClientGeneratedOptimisticId(id)) { + oldOptimisticIds.unshift(id) + } + + if (hasFoundIncludedId) continue + + if (!idsSet.has(id)) { + unincludedIdsIndex = i + } else { + hasFoundIncludedId = true + } + } + + unincludedFirstPageIds = oldIds.slice(unincludedIdsIndex) + unincludedOptimisticIds = oldOptimisticIds.filter( + (id) => !optimisticIds.has(commentIdsOptimisticEncoder.decode(id)) + ) + } + + return { + data: [ + ...unincludedOptimisticIds, + ...messages.map(({ id }) => id), + ...unincludedFirstPageIds, + ], + page, + hasMore, + totalData, + } +} + +const COMMENT_IDS_QUERY_KEY = 'commentsByAccount' +const getQueryKey = (postId: string, account: string) => [ + COMMENT_IDS_QUERY_KEY, + postId, + account, +] +export const getPaginatedPostIdsByPostIdAndAccount = { + getQueryKey, + getFirstPageData: (client: QueryClient, postId: string, account: string) => { + const cachedData = client?.getQueryData(getQueryKey(postId, account)) + return (cachedData as any)?.pages?.[0] as PaginatedPostsData | undefined + }, + fetchFirstPageQuery: async ( + client: QueryClient | null, + postId: string, + account: string, + page = 1 + ) => { + const res = await getPaginatedPostIdsByRootPostIdAndAccount({ + postId, + page, + account, + client, + }) + if (!client) return res + + client.setQueryData(getQueryKey(postId, account), { + pageParams: [1], + pages: [res], + }) + return res + }, + setQueryFirstPageData: ( + client: QueryClient, + postId: string, + updater: (oldIds?: string[]) => string[] | undefined | null, + account: string + ) => { + client.setQueryData(getQueryKey(postId, account), (oldData: any) => { + const firstPage = oldData?.pages?.[0] as PaginatedPostsData | undefined + const newPages = [...(oldData?.pages ?? [])] + const newFirstPageMessageIds = updater(firstPage?.data) + newPages.splice(0, 1, { + ...firstPage, + data: newFirstPageMessageIds, + totalData: newFirstPageMessageIds?.length ?? 0, + } as PaginatedPostsData) + return { + pageParams: [...(oldData?.pageParams ?? [])], + pages: [...newPages], + } + }) + }, + invalidateFirstQuery: ( + client: QueryClient, + postId: string, + account: string + ) => { + client.invalidateQueries(getQueryKey(postId, account), { + refetchPage: (_, index) => index === 0, + }) + }, + useInfiniteQuery: ( + postId: string, + account: string, + config?: QueryClientConfig + ): UseInfiniteQueryResult => { + const client = useQueryClient() + return useInfiniteQuery({ + ...config, + queryKey: getQueryKey(postId, account), + queryFn: async ({ pageParam = 1, queryKey }) => { + const [_, postId] = queryKey + const res = await getPaginatedPostIdsByRootPostIdAndAccount({ + postId, + page: pageParam, + account, + client, + }) + + // hotfix because in offchain chat (/offchain/18634) its not updating cache when first invalidated from server + if (pageParam === 1) { + client.setQueryData<{ + pageParams: number[] + pages: PaginatedPostsData[] + }>(getQueryKey(postId, account), (oldData) => { + if ( + !oldData || + !Array.isArray(oldData.pageParams) || + !Array.isArray(oldData.pages) + ) + return oldData + const pages = [...oldData.pages] + pages.splice(0, 1, res) + return { + ...oldData, + pageParams: [...oldData.pageParams], + pages, + } + }) + } + + return res + }, + getNextPageParam: (lastPage) => + lastPage.hasMore ? lastPage.page + 1 : undefined, + }) + }, +} From 646759ad37885930aa600f73bec10d4e15dfc327 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 21:38:27 +0700 Subject: [PATCH 11/35] Insert data based on create time --- src/services/datahub/generated-query.ts | 2 ++ src/services/datahub/posts/subscription.tsx | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/services/datahub/generated-query.ts b/src/services/datahub/generated-query.ts index c84cef7b3..d50490920 100644 --- a/src/services/datahub/generated-query.ts +++ b/src/services/datahub/generated-query.ts @@ -2865,6 +2865,7 @@ export type SubscribePostSubscription = { optimisticId?: string | null dataType: DataType approvedInRootPost: boolean + createdAtTime?: any | null rootPost?: { __typename?: 'Post'; persistentId?: string | null } | null } } @@ -3623,6 +3624,7 @@ export const SubscribePost = gql` optimisticId dataType approvedInRootPost + createdAtTime rootPost { persistentId } diff --git a/src/services/datahub/posts/subscription.tsx b/src/services/datahub/posts/subscription.tsx index 0e79abbd9..e2249495e 100644 --- a/src/services/datahub/posts/subscription.tsx +++ b/src/services/datahub/posts/subscription.tsx @@ -69,6 +69,7 @@ const SUBSCRIBE_POST = gql` optimisticId dataType approvedInRootPost + createdAtTime rootPost { persistentId } @@ -236,7 +237,19 @@ async function processMessage( return newIds } - newIds.unshift(newestId) + const index = oldData.findIndex((id) => { + const data = getPostQuery.getQueryData(queryClient, id) + if (!data) return false + if (data.struct.createdAtTime <= eventData.entity.createdAtTime) { + newIds.unshift(newestId) + return true + } + return false + }) + if (index !== -1) { + newIds.splice(index, 0, newestId) + } + return newIds } ) From 7118f8f02d7a9e0fd4fb17a65136a16fcad197f0 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 22:30:47 +0700 Subject: [PATCH 12/35] Finish showing user's unapproved comment --- src/components/chats/ChatItem/ChatItem.tsx | 1 - .../chats/hooks/usePaginatedMessageIds.ts | 15 +++++- .../extensions/common/CommonChatItem.tsx | 4 +- src/pages/tg/index.tsx | 3 +- src/services/datahub/posts/query.ts | 33 +++++++++--- src/services/datahub/posts/subscription.tsx | 51 ++++++++++++++++--- .../subsocial/commentIds/optimistic.ts | 7 ++- 7 files changed, 95 insertions(+), 19 deletions(-) diff --git a/src/components/chats/ChatItem/ChatItem.tsx b/src/components/chats/ChatItem/ChatItem.tsx index b039906b2..864a27c76 100644 --- a/src/components/chats/ChatItem/ChatItem.tsx +++ b/src/components/chats/ChatItem/ChatItem.tsx @@ -53,7 +53,6 @@ export default function ChatItem({ const canRenderEmbed = useCanRenderEmbed(link ?? '') if (showApproveButton && message.struct.approvedInRootPost) return null - if (!showApproveButton && !message.struct.approvedInRootPost) return null if (!body && (!extensions || extensions.length === 0)) return null diff --git a/src/components/chats/hooks/usePaginatedMessageIds.ts b/src/components/chats/hooks/usePaginatedMessageIds.ts index 4fee2ab78..4993277ae 100644 --- a/src/components/chats/hooks/usePaginatedMessageIds.ts +++ b/src/components/chats/hooks/usePaginatedMessageIds.ts @@ -3,6 +3,7 @@ import { PaginatedPostsData, getPaginatedPostIdsByPostId, } from '@/services/datahub/posts/query' +import { useMyMainAddress } from '@/stores/my-account' import { useMemo } from 'react' type PaginatedData = { @@ -26,11 +27,23 @@ export default function usePaginatedMessageIds({ hubId, onlyDisplayUnapprovedMessages, }: PaginatedConfig): PaginatedData { - const { data, fetchNextPage, isLoading } = + const myAddress = useMyMainAddress() ?? '' + // because from server it doesn't have access to myAddress, so we need to use the data without users' unapproved posts as placeholder + const { data: placeholderData } = getPaginatedPostIdsByPostId.useInfiniteQuery({ postId: chatId, onlyDisplayUnapprovedMessages: !!onlyDisplayUnapprovedMessages, + myAddress: '', }) + const { data, fetchNextPage, isLoading } = + getPaginatedPostIdsByPostId.useInfiniteQuery( + { + postId: chatId, + onlyDisplayUnapprovedMessages: !!onlyDisplayUnapprovedMessages, + myAddress, + }, + { enabled: !!myAddress, placeholderData } + ) const page = data?.pages let lastPage: PaginatedPostsData | null = null diff --git a/src/components/extensions/common/CommonChatItem.tsx b/src/components/extensions/common/CommonChatItem.tsx index fb9765372..bf60fcf4e 100644 --- a/src/components/extensions/common/CommonChatItem.tsx +++ b/src/components/extensions/common/CommonChatItem.tsx @@ -301,7 +301,7 @@ export default function CommonChatItem({ {showApproveButton ? (
- ) : ( + ) : message.struct.approvedInRootPost ? ( + ) : ( + )}
diff --git a/src/pages/tg/index.tsx b/src/pages/tg/index.tsx index 303d47cb8..5d7d34577 100644 --- a/src/pages/tg/index.tsx +++ b/src/pages/tg/index.tsx @@ -14,12 +14,13 @@ async function prefetchChatData(client: QueryClient, chatId: string) { const firstPageData = await getPaginatedPostIdsByPostId.fetchFirstPageQuery( client, - { postId: chatId, onlyDisplayUnapprovedMessages: false }, + { postId: chatId, onlyDisplayUnapprovedMessages: false, myAddress: '' }, 1 ) getPaginatedPostIdsByPostId.invalidateFirstQuery(client, { postId: chatId, onlyDisplayUnapprovedMessages: false, + myAddress: '', }) const ownerIds = firstPageData.data .map((id) => { diff --git a/src/services/datahub/posts/query.ts b/src/services/datahub/posts/query.ts index 575c1cbf0..0b2b0784d 100644 --- a/src/services/datahub/posts/query.ts +++ b/src/services/datahub/posts/query.ts @@ -52,23 +52,28 @@ export type PaginatedPostsData = { hasMore: boolean totalData: number } +type Data = { + postId: string + onlyDisplayUnapprovedMessages: boolean + myAddress: string +} async function getPaginatedPostIdsByRootPostId({ page, postId, client, onlyDisplayUnapprovedMessages, + myAddress, }: { - postId: string page: number client?: QueryClient | null - onlyDisplayUnapprovedMessages: boolean -}): Promise { +} & Data): Promise { if (!postId || !client) return { data: [], page, hasMore: false, totalData: 0 } const oldIds = getPaginatedPostIdsByPostId.getFirstPageData(client, { postId, onlyDisplayUnapprovedMessages, + myAddress, }) const firstPageDataLength = oldIds?.length || CHAT_PER_PAGE @@ -86,10 +91,23 @@ async function getPaginatedPostIdsByRootPostId({ document: GET_COMMENT_IDS_IN_POST_ID, variables: { args: { - filter: { - rootPostId: postId, - approvedInRootPost: !onlyDisplayUnapprovedMessages, - }, + filter: + myAddress && !onlyDisplayUnapprovedMessages + ? { + OR: [ + { + rootPostId: postId, + approvedInRootPost: !onlyDisplayUnapprovedMessages, + }, + { + createdByAccountAddress: myAddress, + }, + ], + } + : { + rootPostId: postId, + approvedInRootPost: !onlyDisplayUnapprovedMessages, + }, orderBy: 'createdAtTime', orderDirection: QueryOrder.Desc, pageSize: CHAT_PER_PAGE, @@ -164,7 +182,6 @@ async function getPaginatedPostIdsByRootPostId({ totalData, } } -type Data = { postId: string; onlyDisplayUnapprovedMessages: boolean } const COMMENT_IDS_QUERY_KEY = 'comments' const getQueryKey = (data: Data) => [COMMENT_IDS_QUERY_KEY, data] export const getPaginatedPostIdsByPostId = { diff --git a/src/services/datahub/posts/subscription.tsx b/src/services/datahub/posts/subscription.tsx index e2249495e..db0899c82 100644 --- a/src/services/datahub/posts/subscription.tsx +++ b/src/services/datahub/posts/subscription.tsx @@ -1,7 +1,7 @@ import Toast from '@/components/Toast' import { getPostQuery } from '@/services/api/query' import { commentIdsOptimisticEncoder } from '@/services/subsocial/commentIds/optimistic' -import { getMyMainAddress } from '@/stores/my-account' +import { getMyMainAddress, useMyMainAddress } from '@/stores/my-account' import { useSubscriptionState } from '@/stores/subscription' import { cx } from '@/utils/class-names' import { QueryClient, useQueryClient } from '@tanstack/react-query' @@ -19,6 +19,7 @@ import { getPaginatedPostIdsByPostId, getPostMetadataQuery } from './query' // Note: careful when using this in several places, if you have 2 places, the first one will be the one subscribing // the subscription will only be one, but if the first place is unmounted, it will unsubscribe, making all other places unsubscribed too export function useDatahubPostSubscriber(subscribedPostId?: string) { + const myAddress = useMyMainAddress() const queryClient = useQueryClient() const unsubRef = useRef<(() => void) | undefined>() const subState = useSubscriptionState( @@ -33,13 +34,14 @@ export function useDatahubPostSubscriber(subscribedPostId?: string) { if (!isDatahubAvailable) return const listener = () => { - if (document.visibilityState === 'visible') { + if (document.visibilityState === 'visible' && myAddress) { unsubRef.current = subscription(queryClient) // invalidate first page so it will refetch after the websocket connection is disconnected previously when the user is not in the tab if (subscribedPostId) { getPaginatedPostIdsByPostId.invalidateFirstQuery(queryClient, { postId: subscribedPostId, onlyDisplayUnapprovedMessages: false, + myAddress, }) } } else { @@ -56,7 +58,7 @@ export function useDatahubPostSubscriber(subscribedPostId?: string) { document.removeEventListener('visibilitychange', listener) unsubRef.current?.() } - }, [queryClient, subscribedPostId]) + }, [queryClient, subscribedPostId, myAddress]) } const SUBSCRIBE_POST = gql` @@ -169,13 +171,14 @@ async function processMessage( } const newPost = getPostQuery.getQueryData(queryClient, newestId) + const myAddress = getMyMainAddress() + const isCurrentOwner = newPost?.struct.ownerId === myAddress if (isCreationEvent) { const tokenomics = await getTokenomicsMetadataQuery.fetchQuery( queryClient, null ) - const myAddress = getMyMainAddress() - if (newPost?.struct.ownerId === myAddress && isCreationEvent) { + if (isCurrentOwner && isCreationEvent) { if (newPost.struct.approvedInRootPost) { toast.custom((t) => ( { + if (!oldData) return oldData + const oldIdsSet = new Set(oldData) + if (oldIdsSet.has(newestId)) return oldData + + const newIds = [...oldData] + const index = oldData.findIndex((id) => { + const data = getPostQuery.getQueryData(queryClient, id) + if (!data) return false + if (data.struct.createdAtTime <= eventData.entity.createdAtTime) { + newIds.unshift(newestId) + return true + } + return false + }) + if (index !== -1) { + newIds.splice(index, 0, newestId) + } + + return newIds + } + ) + } + getPaginatedPostIdsByPostId.setQueryFirstPageData( queryClient, { postId: rootPostId, onlyDisplayUnapprovedMessages: !newPost?.struct.approvedInRootPost, + myAddress: getMyMainAddress() ?? '', }, (oldData) => { if (!oldData) return oldData @@ -240,7 +276,10 @@ async function processMessage( const index = oldData.findIndex((id) => { const data = getPostQuery.getQueryData(queryClient, id) if (!data) return false - if (data.struct.createdAtTime <= eventData.entity.createdAtTime) { + if ( + new Date(data.struct.createdAtTime) <= + new Date(eventData.entity.createdAtTime) + ) { newIds.unshift(newestId) return true } diff --git a/src/services/subsocial/commentIds/optimistic.ts b/src/services/subsocial/commentIds/optimistic.ts index fb42af2f6..9718c380a 100644 --- a/src/services/subsocial/commentIds/optimistic.ts +++ b/src/services/subsocial/commentIds/optimistic.ts @@ -10,6 +10,7 @@ import { getPaginatedPostIdsByPostId, getPostMetadataQuery, } from '@/services/datahub/posts/query' +import { getMyMainAddress } from '@/stores/my-account' import type { SendMessageParams } from './types' export const commentIdsOptimisticEncoder = { @@ -46,7 +47,11 @@ export function addOptimisticData({ } as unknown as PostData) getPaginatedPostIdsByPostId.setQueryFirstPageData( client, - { onlyDisplayUnapprovedMessages: false, postId: params.chatId }, + { + onlyDisplayUnapprovedMessages: false, + postId: params.chatId, + myAddress: getMyMainAddress() || '', + }, (oldData) => { return [newId, ...(oldData ?? [])] } From 5b85edad57b10b426ac13f7888ae01e41e3de8ba Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 22:34:56 +0700 Subject: [PATCH 13/35] Change style of pending review --- src/components/extensions/common/CommonChatItem.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/extensions/common/CommonChatItem.tsx b/src/components/extensions/common/CommonChatItem.tsx index bf60fcf4e..56cef9473 100644 --- a/src/components/extensions/common/CommonChatItem.tsx +++ b/src/components/extensions/common/CommonChatItem.tsx @@ -310,7 +310,11 @@ export default function CommonChatItem({ className='mb-1.5 ml-2.5 mt-1 self-start' /> ) : ( - +
+
+ Pending Review +
+
)}
From 2eb89ba0fc57e50c96df67f10d275222724fac0d Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 22:38:13 +0700 Subject: [PATCH 14/35] Change style of pending review --- src/components/extensions/common/CommonChatItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/extensions/common/CommonChatItem.tsx b/src/components/extensions/common/CommonChatItem.tsx index 56cef9473..e8236ff42 100644 --- a/src/components/extensions/common/CommonChatItem.tsx +++ b/src/components/extensions/common/CommonChatItem.tsx @@ -311,8 +311,8 @@ export default function CommonChatItem({ /> ) : (
-
- Pending Review +
+ ⌛ Pending Review
)} From 9445ed1cafa08bbd9b6c4b1d63697e252db1b63a Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 22:55:35 +0700 Subject: [PATCH 15/35] Add loading to approve button --- src/components/extensions/common/CommonChatItem.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/extensions/common/CommonChatItem.tsx b/src/components/extensions/common/CommonChatItem.tsx index b104b01da..ea866f3b0 100644 --- a/src/components/extensions/common/CommonChatItem.tsx +++ b/src/components/extensions/common/CommonChatItem.tsx @@ -352,12 +352,13 @@ function ApproveButton({ ownerId: string }) { const { data: profile } = getProfileQuery.useQuery(ownerId) - const { mutate } = useApproveUser() + const { mutate, isLoading } = useApproveUser() return ( +
{/* My EVM Address - - {truncateAddress(evmAddress ?? '')} - +
+ + {truncateAddress(evmAddress ?? '')} + + +
{/* Date: Tue, 9 Jul 2024 23:49:38 +0700 Subject: [PATCH 17/35] Add datahub health checker --- src/pages/_app.tsx | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index cf0f57d4e..ef0a63224 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,6 +1,7 @@ import ErrorBoundary from '@/components/ErrorBoundary' import HeadConfig, { HeadConfigProps } from '@/components/HeadConfig' import Spinner from '@/components/Spinner' +import Toast from '@/components/Toast' import GlobalModals from '@/components/modals/GlobalModals' import { ReferralUrlChanger } from '@/components/referral/ReferralUrlChanger' import { env } from '@/env.mjs' @@ -10,6 +11,7 @@ import useSaveTappedPointsAndEnergy, { } from '@/modules/telegram/TapPage/useSaveTappedPointsAndEnergy' import { ConfigProvider } from '@/providers/config/ConfigProvider' import EvmProvider from '@/providers/evm/EvmProvider' +import { getDatahubHealthQuery } from '@/services/datahub/health/query' import { getLinkedIdentityQuery } from '@/services/datahub/identity/query' import { increaseEnergyValue } from '@/services/datahub/leaderboard/points-balance/optimistic' import { FULL_ENERGY_VALUE } from '@/services/datahub/leaderboard/points-balance/query' @@ -33,7 +35,7 @@ import type { AppProps } from 'next/app' import Script from 'next/script' import React, { useEffect, useRef, useState } from 'react' import { isDesktop } from 'react-device-detect' -import { Toaster } from 'sonner' +import { Toaster, toast } from 'sonner' import urlJoin from 'url-join' export type AppCommonProps = { @@ -139,6 +141,7 @@ function AppContent({ Component, pageProps }: AppProps) { +
@@ -262,3 +265,31 @@ function SessionAccountChecker() { return null } + +function DatahubHealthChecker() { + const { data } = getDatahubHealthQuery.useQuery(null, { + refetchInterval: 10_000, + }) + const currentId = useRef('') + useEffect(() => { + if (!data) { + if (currentId.current) return + const id = toast.custom( + (t) => ( + + ), + { duration: Infinity, dismissible: false } + ) + currentId.current = id + } else { + toast.dismiss(currentId.current) + currentId.current = '' + } + }, [data]) + + return null +} From 5ae62c05e21971f69ec265ec1abfb1934ff0b295 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 23:49:49 +0700 Subject: [PATCH 18/35] Remove unused func --- src/services/datahub/generalStats/query.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/services/datahub/generalStats/query.ts b/src/services/datahub/generalStats/query.ts index 94f0d81aa..1839717b8 100644 --- a/src/services/datahub/generalStats/query.ts +++ b/src/services/datahub/generalStats/query.ts @@ -8,9 +8,6 @@ import { datahubQueryRequest } from '../utils' const generalStatsId = 'generalStatsId' -export const getGeneralStatsData = () => - getGeneralStatsQuery.useQuery(generalStatsId) - const GET_GENERAL_STATS = gql` query GetGeneralStats { activeStakingTotalActivityMetricsForFixedPeriod( From f9bb3174c3512c41e56899b7e35c017a39ae71c5 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 23:50:01 +0700 Subject: [PATCH 19/35] Add datahub health api --- src/services/datahub/health/query.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/services/datahub/health/query.ts diff --git a/src/services/datahub/health/query.ts b/src/services/datahub/health/query.ts new file mode 100644 index 000000000..993a73dd0 --- /dev/null +++ b/src/services/datahub/health/query.ts @@ -0,0 +1,21 @@ +import { env } from '@/env.mjs' +import { createQuery } from '@/subsocial-query' +import axios from 'axios' +import urlJoin from 'url-join' + +export const getDatahubHealthQuery = createQuery({ + key: 'datahubHealth', + fetcher: async () => { + try { + const res = await axios.get( + urlJoin( + env.NEXT_PUBLIC_DATAHUB_QUERY_URL.replace(/\/graphql\/?$/, ''), + '/healthcheck/status' + ) + ) + return res.data.operational as boolean + } catch { + return false + } + }, +}) From 49db2a5c2cda4d9d7ae9d5b4fce8d9dcdef5ef92 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 9 Jul 2024 23:58:42 +0700 Subject: [PATCH 20/35] Not open error toast at first launch --- src/pages/_app.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index ef0a63224..40515b99b 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -272,6 +272,7 @@ function DatahubHealthChecker() { }) const currentId = useRef('') useEffect(() => { + if (typeof data !== 'boolean') return if (!data) { if (currentId.current) return const id = toast.custom( From 53eb6d3a91a3c2cd976f7b59f7e9854224185dee Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 10 Jul 2024 00:13:38 +0700 Subject: [PATCH 21/35] Add stale time to refetch --- src/modules/chat/HomePage/ChatContent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/chat/HomePage/ChatContent.tsx b/src/modules/chat/HomePage/ChatContent.tsx index 63c47eebe..5e49b7299 100644 --- a/src/modules/chat/HomePage/ChatContent.tsx +++ b/src/modules/chat/HomePage/ChatContent.tsx @@ -276,6 +276,7 @@ function PostMemeButton({ const { data: timeLeftFromApi, isLoading: loadingTimeLeft } = getTimeLeftUntilCanPostQuery.useQuery(myAddress, { refetchOnWindowFocus: true, + staleTime: 0, }) const { threshold, isLoading: loadingThreshold } = From 972b7e351bd07db7eb3d2f2e7c82e28fb402ebe5 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 10 Jul 2024 00:22:48 +0700 Subject: [PATCH 22/35] Refetch using manual visibility change --- src/modules/chat/HomePage/ChatContent.tsx | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/modules/chat/HomePage/ChatContent.tsx b/src/modules/chat/HomePage/ChatContent.tsx index 5e49b7299..196a188f0 100644 --- a/src/modules/chat/HomePage/ChatContent.tsx +++ b/src/modules/chat/HomePage/ChatContent.tsx @@ -273,11 +273,21 @@ function PostMemeButton({ const myAddress = useMyMainAddress() ?? '' const { data, isLoading } = getBalanceQuery.useQuery(myAddress) - const { data: timeLeftFromApi, isLoading: loadingTimeLeft } = - getTimeLeftUntilCanPostQuery.useQuery(myAddress, { - refetchOnWindowFocus: true, - staleTime: 0, - }) + + const { + data: timeLeftFromApi, + isLoading: loadingTimeLeft, + refetch, + } = getTimeLeftUntilCanPostQuery.useQuery(myAddress) + useEffect(() => { + const listener = () => { + if (document.visibilityState === 'visible') refetch() + } + document.addEventListener('visibilitychange', listener, false) + return () => { + document.removeEventListener('visibilitychange', listener) + } + }, [refetch]) const { threshold, isLoading: loadingThreshold } = usePostMemeThreshold(chatId) From 135f20a4d312bfae7e123d6b37f11b6a52e1b837 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 10 Jul 2024 00:29:48 +0700 Subject: [PATCH 23/35] Fix image modal --- src/components/extensions/image/ImageModal.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/extensions/image/ImageModal.tsx b/src/components/extensions/image/ImageModal.tsx index 8f71ef12c..3963c2141 100644 --- a/src/components/extensions/image/ImageModal.tsx +++ b/src/components/extensions/image/ImageModal.tsx @@ -222,9 +222,12 @@ function ImageUpload({ initialImage, setUploadedImageLink }: ImageUploadProps) { setImageUrl('')} src={imageUrl} - onLoad={() => - setUploadedImageLink((prev) => ({ ...prev, loadedLink: imageUrl })) - } + onLoad={() => { + // To prevent this called first before the useEffect, which causes image rendered, but the link is null + setTimeout(() => { + setUploadedImageLink((prev) => ({ ...prev, loadedLink: imageUrl })) + }) + }} /> ) } From 947e66855e97a4cf06fb68c6eac1b88898138dc9 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 10 Jul 2024 00:43:40 +0700 Subject: [PATCH 24/35] Change time constraint to use env --- .github/workflows/build-deploy.yml | 2 ++ ci.env | 1 + docker/Dockerfile | 4 ++++ src/constants/chat-rules.ts | 1 - src/env.mjs | 2 ++ src/services/datahub/posts/mutation.ts | 4 ++-- src/services/datahub/posts/query.ts | 6 +++--- 7 files changed, 14 insertions(+), 6 deletions(-) delete mode 100644 src/constants/chat-rules.ts diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml index 9dc03278a..9de4ac6dc 100644 --- a/.github/workflows/build-deploy.yml +++ b/.github/workflows/build-deploy.yml @@ -60,6 +60,7 @@ jobs: GH_NEXT_PUBLIC_CONTEST_CHAT_ID=0x0106b70599fea6682ec0de3c6ab248d4 GH_NEXT_PUBLIC_CONTEST_NAME=MEMECOIN CONTEST GH_NEXT_PUBLIC_CONTEST_END_TIME=1720796400739 + GH_NEXT_PUBLIC_TIME_CONSTRAINT=300000 GH_NEXT_PUBLIC_AMP_ID=40d4174295c7edf657fc3bedf2748549 GH_NEXT_PUBLIC_COMMUNITY_HUB_ID=12455 GH_NEXT_PUBLIC_GA_ID=G-TP1XEFNHQD @@ -113,6 +114,7 @@ jobs: GH_NEXT_PUBLIC_CONTEST_CHAT_ID=0x850f0f5c0c244eba16425a464b0becfc GH_NEXT_PUBLIC_CONTEST_NAME=MEMECOIN CONTEST GH_NEXT_PUBLIC_CONTEST_END_TIME=1720796400739 + GH_NEXT_PUBLIC_TIME_CONSTRAINT=5000 GH_NEXT_PUBLIC_TELEGRAM_NOTIFICATION_BOT=https://t.me/g_notif_staging_bot/ GH_TELEGRAM_BOT_TOKEN="7038999347:AAGBgXTWcXpR4vZPW9A8_ia9PkWOpeyDeWA" # without base path diff --git a/ci.env b/ci.env index c5b6d4605..a9f4e29ef 100644 --- a/ci.env +++ b/ci.env @@ -7,6 +7,7 @@ NEXT_PUBLIC_MAIN_CHAT_ID='$GH_NEXT_PUBLIC_MAIN_CHAT_ID' NEXT_PUBLIC_CONTEST_CHAT_ID='$GH_NEXT_PUBLIC_CONTEST_CHAT_ID' NEXT_PUBLIC_CONTEST_NAME='$GH_NEXT_PUBLIC_CONTEST_NAME' NEXT_PUBLIC_CONTEST_END_TIME='$GH_NEXT_PUBLIC_CONTEST_END_TIME' +NEXT_PUBLIC_TIME_CONSTRAINT='$GH_NEXT_PUBLIC_TIME_CONSTRAINT' NEXT_PUBLIC_BASE_PATH='$GH_NEXT_PUBLIC_BASE_PATH' NEXT_PUBLIC_NEYNAR_CLIENT_ID='$GH_NEXT_PUBLIC_NEYNAR_CLIENT_ID' NEXT_PUBLIC_TELEGRAM_BOT_ID='$GH_NEXT_PUBLIC_TELEGRAM_BOT_ID' diff --git a/docker/Dockerfile b/docker/Dockerfile index 076cc58ae..c9dfe4402 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -14,6 +14,7 @@ ARG GH_NEXT_PUBLIC_MAIN_CHAT_ID ARG GH_NEXT_PUBLIC_CONTEST_CHAT_ID ARG GH_NEXT_PUBLIC_CONTEST_NAME ARG GH_NEXT_PUBLIC_CONTEST_END_TIME +ARG GH_NEXT_PUBLIC_TIME_CONSTRAINT ARG GH_NEXT_PUBLIC_BASE_PATH ARG GH_NEXT_PUBLIC_NEYNAR_CLIENT_ID ARG GH_NEXT_PUBLIC_TELEGRAM_BOT_ID @@ -42,6 +43,7 @@ ENV NEXTAUTH_URL=${GH_NEXTAUTH_URL} \ NEXT_PUBLIC_CONTEST_CHAT_ID=${GH_NEXT_PUBLIC_CONTEST_CHAT_ID} \ NEXT_PUBLIC_CONTEST_NAME=${GH_NEXT_PUBLIC_CONTEST_NAME} \ NEXT_PUBLIC_CONTEST_END_TIME=${GH_NEXT_PUBLIC_CONTEST_END_TIME} \ + NEXT_PUBLIC_TIME_CONSTRAINT=${GH_NEXT_PUBLIC_TIME_CONSTRAINT} \ NEXT_PUBLIC_BASE_PATH=${GH_NEXT_PUBLIC_BASE_PATH} \ NEXT_PUBLIC_NEYNAR_CLIENT_ID=${GH_NEXT_PUBLIC_NEYNAR_CLIENT_ID} \ NEXT_PUBLIC_TELEGRAM_BOT_ID=${GH_NEXT_PUBLIC_TELEGRAM_BOT_ID} \ @@ -100,6 +102,7 @@ ARG GH_NEXT_PUBLIC_MAIN_CHAT_ID ARG GH_NEXT_PUBLIC_CONTEST_CHAT_ID ARG GH_NEXT_PUBLIC_CONTEST_NAME ARG GH_NEXT_PUBLIC_CONTEST_END_TIME +ARG GH_NEXT_PUBLIC_TIME_CONSTRAINT ARG GH_NEXT_PUBLIC_BASE_PATH ARG GH_NEXT_PUBLIC_NEYNAR_CLIENT_ID ARG GH_NEXT_PUBLIC_TELEGRAM_BOT_ID @@ -128,6 +131,7 @@ ENV NEXTAUTH_URL=${GH_NEXTAUTH_URL} \ NEXT_PUBLIC_CONTEST_CHAT_ID=${GH_NEXT_PUBLIC_CONTEST_CHAT_ID} \ NEXT_PUBLIC_CONTEST_NAME=${GH_NEXT_PUBLIC_CONTEST_NAME} \ NEXT_PUBLIC_CONTEST_END_TIME=${GH_NEXT_PUBLIC_CONTEST_END_TIME} \ + NEXT_PUBLIC_TIME_CONSTRAINT=${GH_NEXT_PUBLIC_TIME_CONSTRAINT} \ NEXT_PUBLIC_BASE_PATH=${GH_NEXT_PUBLIC_BASE_PATH} \ NEXT_PUBLIC_NEYNAR_CLIENT_ID=${GH_NEXT_PUBLIC_NEYNAR_CLIENT_ID} \ NEXT_PUBLIC_TELEGRAM_BOT_ID=${GH_NEXT_PUBLIC_TELEGRAM_BOT_ID} \ diff --git a/src/constants/chat-rules.ts b/src/constants/chat-rules.ts deleted file mode 100644 index fd0a576ea..000000000 --- a/src/constants/chat-rules.ts +++ /dev/null @@ -1 +0,0 @@ -export const TIME_CONSTRAINT = 5 * 60 * 1000 // 5 mins diff --git a/src/env.mjs b/src/env.mjs index da997bde6..449bea3c7 100644 --- a/src/env.mjs +++ b/src/env.mjs @@ -44,6 +44,7 @@ export const env = createEnv({ NEXT_PUBLIC_CONTEST_CHAT_ID: z.string().default(''), NEXT_PUBLIC_CONTEST_NAME: z.string().default(''), NEXT_PUBLIC_CONTEST_END_TIME: z.string().default('').transform(Number), + NEXT_PUBLIC_TIME_CONSTRAINT: z.string().default('').transform(Number), NEXT_PUBLIC_BASE_PATH: z.string().default(''), NEXT_PUBLIC_TELEGRAM_BOT_ID: z.string().default(''), NEXT_PUBLIC_TELEGRAM_BOT_USERNAME: z.string().default(''), @@ -85,6 +86,7 @@ export const env = createEnv({ NEXT_PUBLIC_CONTEST_CHAT_ID: process.env.NEXT_PUBLIC_CONTEST_CHAT_ID, NEXT_PUBLIC_CONTEST_NAME: process.env.NEXT_PUBLIC_CONTEST_NAME, NEXT_PUBLIC_CONTEST_END_TIME: process.env.NEXT_PUBLIC_CONTEST_END_TIME, + NEXT_PUBLIC_TIME_CONSTRAINT: process.env.NEXT_PUBLIC_TIME_CONSTRAINT, NEXT_PUBLIC_TELEGRAM_BOT_ID: process.env.NEXT_PUBLIC_TELEGRAM_BOT_ID, NEXT_PUBLIC_TELEGRAM_BOT_USERNAME: process.env.NEXT_PUBLIC_TELEGRAM_BOT_USERNAME, diff --git a/src/services/datahub/posts/mutation.ts b/src/services/datahub/posts/mutation.ts index a5961661b..8e2b24e13 100644 --- a/src/services/datahub/posts/mutation.ts +++ b/src/services/datahub/posts/mutation.ts @@ -1,5 +1,5 @@ import { getMaxMessageLength } from '@/constants/chat' -import { TIME_CONSTRAINT } from '@/constants/chat-rules' +import { env } from '@/env.mjs' import { ApiDatahubPostMutationBody, ApiDatahubPostResponse, @@ -257,7 +257,7 @@ export function useSendMessage( getTimeLeftUntilCanPostQuery.setQueryData( queryClient, myAddress, - TIME_CONSTRAINT + env.NEXT_PUBLIC_TIME_CONSTRAINT ) } }, diff --git a/src/services/datahub/posts/query.ts b/src/services/datahub/posts/query.ts index 915102024..402d382cb 100644 --- a/src/services/datahub/posts/query.ts +++ b/src/services/datahub/posts/query.ts @@ -1,5 +1,5 @@ import { CHAT_PER_PAGE } from '@/constants/chat' -import { TIME_CONSTRAINT } from '@/constants/chat-rules' +import { env } from '@/env.mjs' import { getPostQuery, getServerTime } from '@/services/api/query' import { queryClient } from '@/services/provider' import { QueryConfig, createQuery, poolQuery } from '@/subsocial-query' @@ -523,8 +523,8 @@ async function getTimeLeftUntilCanPost(address: string) { } if (!lastPosted) return Infinity - const timeLeft = lastPosted + TIME_CONSTRAINT - serverTime - return Math.min(Math.max(timeLeft, 0), TIME_CONSTRAINT) + const timeLeft = lastPosted + env.NEXT_PUBLIC_TIME_CONSTRAINT - serverTime + return Math.min(Math.max(timeLeft, 0), env.NEXT_PUBLIC_TIME_CONSTRAINT) } export const getTimeLeftUntilCanPostQuery = createQuery({ key: 'lastPostedMeme', From 2f2e2a00c26bdedcd9316160cb2b23a3212f43e8 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 10 Jul 2024 14:47:56 +0300 Subject: [PATCH 25/35] Add fix for profile posts list modal --- src/components/ProfilePreviewModalWrapper.tsx | 34 ++-- src/components/chats/ChatItem/ChatItem.tsx | 27 ++- .../chats/ChatItem/ChatItemMenus.tsx | 99 +--------- .../profilePosts/ProfileProstsListModal.tsx | 178 ++++++++---------- src/components/chats/ChatList/ChatList.tsx | 2 + src/stores/profile-posts-modal.ts | 33 ++++ 6 files changed, 144 insertions(+), 229 deletions(-) create mode 100644 src/stores/profile-posts-modal.ts diff --git a/src/components/ProfilePreviewModalWrapper.tsx b/src/components/ProfilePreviewModalWrapper.tsx index 3caf97638..6fa3bda28 100644 --- a/src/components/ProfilePreviewModalWrapper.tsx +++ b/src/components/ProfilePreviewModalWrapper.tsx @@ -1,8 +1,8 @@ +import { useProfilePostsModal } from '@/stores/profile-posts-modal' import { cx } from '@/utils/class-names' import { useState } from 'react' import Name, { NameProps } from './Name' import ProfilePreview from './ProfilePreview' -import ProfilePostsListModalWrapper from './chats/ChatItem/profilePosts/ProfileProstsListModal' import Modal from './modals/Modal' export type ProfilePreviewModalWrapperProps = { @@ -49,26 +49,20 @@ export function ProfilePreviewModalName({ hubId: string enableProfileModal?: boolean }) { + const { openModal } = useProfilePostsModal() + return ( - { + if (enableProfileModal) { + e.preventDefault() + openModal({ messageId, chatId, hubId, address: props.address }) + props.onClick?.(e) + } + }} + className={cx('cursor-pointer', props.className)} address={props.address} - messageId={messageId} - chatId={chatId} - hubId={hubId} - > - {(onClick) => ( - { - if (enableProfileModal) { - onClick(e) - props.onClick?.(e) - } - }} - className={cx('cursor-pointer', props.className)} - address={props.address} - /> - )} - + /> ) } diff --git a/src/components/chats/ChatItem/ChatItem.tsx b/src/components/chats/ChatItem/ChatItem.tsx index 627b5fe0b..d9d04e823 100644 --- a/src/components/chats/ChatItem/ChatItem.tsx +++ b/src/components/chats/ChatItem/ChatItem.tsx @@ -1,4 +1,5 @@ import AddressAvatar from '@/components/AddressAvatar' +import { useProfilePostsModal } from '@/stores/profile-posts-modal' import { cx } from '@/utils/class-names' import { PostData } from '@subsocial/api/types' import { ComponentProps } from 'react' @@ -6,7 +7,6 @@ import { ScrollToMessage } from '../ChatList/hooks/useScrollToMessage' import ChatItemMenus from './ChatItemMenus' import ChatItemWithExtension from './ChatItemWithExtension' import Embed, { useCanRenderEmbed } from './Embed' -import ProfilePostsListModalWrapper from './profilePosts/ProfileProstsListModal' import DefaultChatItem from './variants/DefaultChatItem' import EmojiChatItem, { shouldRenderEmojiChatItem, @@ -40,6 +40,7 @@ export default function ChatItem({ }: ChatItemProps) { const { ownerId, id: messageId } = message.struct const { body, extensions, link } = message.content || {} + const { openModal } = useProfilePostsModal() const canRenderEmbed = useCanRenderEmbed(link ?? '') @@ -61,20 +62,17 @@ export default function ChatItem({ )} > {!isMyMessage && ( - { + e.preventDefault() + + if (enableProfileModal) { + openModal({ chatId, hubId, messageId, address: ownerId }) + } + }} address={ownerId} - chatId={chatId} - hubId={hubId} - messageId={messageId} - > - {(onClick) => ( - - )} - + className='flex-shrink-0 cursor-pointer' + /> )} setMessageAsReply()} {...referenceProps} id={messageBubbleId} > diff --git a/src/components/chats/ChatItem/ChatItemMenus.tsx b/src/components/chats/ChatItem/ChatItemMenus.tsx index f227d6a65..0253f4a1d 100644 --- a/src/components/chats/ChatItem/ChatItemMenus.tsx +++ b/src/components/chats/ChatItem/ChatItemMenus.tsx @@ -62,7 +62,6 @@ export default function ChatItemMenus({ hubId, enableChatMenu = true, }: ChatItemMenusProps) { - // const canSendMessage = useCanSendMessage(hubId, chatId) const myAddress = useMyMainAddress() const isOpen = useChatMenu((state) => state.openedChatId === messageId) @@ -71,26 +70,14 @@ export default function ChatItemMenus({ const { data: post } = getPostQuery.useQuery(messageId) const ownerId = post?.struct.ownerId ?? '' - const { ref, inView } = useInView({ triggerOnce: true }) - // const { evmAddress } = useLinkedEvmAddress(ownerId, { enabled: inView }) - // const refSearchParam = useReferralSearchParam() + const { ref } = useInView({ triggerOnce: true }) - // const router = useRouter() - - // const address = useMyMainAddress() const { data: message } = getPostQuery.useQuery(messageId) const [modalState, setModalState] = useState(null) const { mutate: moderate } = useModerateWithSuccessToast(messageId, chatId) const sendEvent = useSendEvent() - // const openDonateExtension = useOpenDonateExtension( - // message?.id, - // message?.struct.ownerId ?? '' - // ) - - // const setReplyTo = useMessageData((state) => state.setReplyTo) - // const setMessageToEdit = useMessageData((state) => state.setMessageToEdit) const { isAuthorized } = useAuthorizedForModeration(chatId) const { data: reasons } = getModerationReasonsQuery.useQuery(null) @@ -102,40 +89,7 @@ export default function ChatItemMenus({ const pinUnpinMenu = usePinUnpinMenuItem(chatId, messageId) const getChatMenus = (): FloatingMenusProps['menus'] => { - const menus: FloatingMenusProps['menus'] = [ - // { - // text: 'Copy Text', - // icon: MdContentCopy, - // onClick: () => { - // copyToClipboard(message?.content?.body ?? '') - // toast.custom((t) => ( - // - // )) - // }, - // }, - // { - // text: 'Copy Message Link', - // icon: FiLink, - // onClick: () => { - // const messageLink = urlJoin( - // getCurrentUrlOrigin(), - // env.NEXT_PUBLIC_BASE_PATH, - // '/message', - // `/${messageId}`, - // refSearchParam - // ) - // copyToClipboard(messageLink) - // toast.custom((t) => ( - // - // )) - // }, - // }, - // { - // text: 'Show Metadata', - // icon: RiDatabase2Line, - // onClick: () => setModalState('metadata'), - // }, - ] + const menus: FloatingMenusProps['menus'] = [] const hideMenu: FloatingMenusProps['menus'][number] = { text: 'Hide', @@ -191,56 +145,7 @@ export default function ChatItemMenus({ if (isOptimisticMessage) return menus - // const donateMenuItem: FloatingMenusProps['menus'][number] = { - // text: 'Donate', - // icon: RiCopperCoinLine, - // onClick: () => { - // sendEventWithRef(myAddress ?? '', (refId) => { - // sendEvent('click_donate', { postId: messageId }, { ref: refId }) - // }) - // if (!address) { - // useLoginModal.getState().setIsOpen(true) - // return - // } - - // sendEvent('open_donate_action_modal', { hubId, chatId }) - // openDonateExtension() - // }, - // } - // const replyItem: FloatingMenusProps['menus'][number] = { - // text: 'Reply', - // icon: LuReply, - // onClick: () => { - // sendEventWithRef(myAddress ?? '', (refId) => { - // sendEvent( - // 'click_reply', - // { - // eventSource: 'message_menu', - // postId: messageId, - // }, - // { ref: refId } - // ) - // }) - // setReplyTo(messageId) - // }, - // } - // const editItem: FloatingMenusProps['menus'][number] = { - // text: 'Edit', - // icon: LuPencil, - // onClick: () => setMessageToEdit(messageId), - // } - // const showDonateMenuItem = canSendMessage && !isMessageOwner && evmAddress - - // if (showDonateMenuItem) menus.unshift(donateMenuItem) if (pinUnpinMenu) menus.unshift(pinUnpinMenu) - // if (canSendMessage && isMessageOwner) menus.unshift(editItem) - // if (message) - // menus.unshift({ - // text: 'Share', - // icon: GrShareOption, - // submenus: getShareMessageMenus(message), - // }) - // if (canSendMessage) menus.unshift(replyItem) return menus } diff --git a/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx b/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx index 06c15ceab..292f3b89c 100644 --- a/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx +++ b/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx @@ -5,38 +5,28 @@ import useAuthorizedForModeration from '@/hooks/useAuthorizedForModeration' import { getModerationReasonsQuery } from '@/services/datahub/moderation/query' import { getPaginatedPostIdsByPostIdAndAccount } from '@/services/datahub/posts/queryByAccount' import { useSendEvent } from '@/stores/analytics' +import { useProfilePostsModal } from '@/stores/profile-posts-modal' import { cx } from '@/utils/class-names' import { Transition } from '@headlessui/react' -import { useQueryClient } from '@tanstack/react-query' -import { useState } from 'react' import { createPortal } from 'react-dom' import { HiOutlineChevronLeft } from 'react-icons/hi2' import SkeletonFallback from '../../../SkeletonFallback' import { useModerateWithSuccessToast } from '../ChatItemMenus' import ProfilePostsList from './ProfilePostsList' -type ProfilePostsListModalProps = { - address: string - children: ( - onClick: (e: { stopPropagation: () => void }) => void - ) => React.ReactNode - messageId: string - chatId: string - hubId: string -} +const ProfilePostsListModal = () => { + const { + isOpen, + closeModal, + messageId = '', + chatId = '', + hubId = '', + address = '', + } = useProfilePostsModal() -const ProfilePostsListModalWrapper = ({ - children, - address, - messageId, - chatId, - hubId, -}: ProfilePostsListModalProps) => { - const [isOpen, setIsOpen] = useState(false) const { mutate: moderate } = useModerateWithSuccessToast(messageId, chatId) const sendEvent = useSendEvent() const { isAuthorized } = useAuthorizedForModeration(chatId) - const client = useQueryClient() const { data: reasons } = getModerationReasonsQuery.useQuery(null) const firstReasonId = reasons?.[0].id @@ -58,90 +48,84 @@ const ProfilePostsListModalWrapper = ({ }, chatId, }) + + closeModal() } - return ( + return createPortal( <> - {children((e) => { - e.stopPropagation() - setIsOpen(true) - })} - {createPortal( - <> - - -
-
-
- - -
- - - Memes: - - {totalPostsCount} - - -
-
+ + +
+
+
+ + +
+ + + Memes: + - Block user - - )} -
-
- + {totalPostsCount} + +
- - , - document.body - )} - + + {isAuthorized && ( + + )} +
+
+ +
+
+
+ , + document.body ) } -export default ProfilePostsListModalWrapper +export default ProfilePostsListModal diff --git a/src/components/chats/ChatList/ChatList.tsx b/src/components/chats/ChatList/ChatList.tsx index 9164e89b4..b341a7c55 100644 --- a/src/components/chats/ChatList/ChatList.tsx +++ b/src/components/chats/ChatList/ChatList.tsx @@ -10,6 +10,7 @@ import { cx } from '@/utils/class-names' import { sendMessageToParentWindow } from '@/utils/window' import { ComponentProps, Fragment, useEffect, useId, useRef } from 'react' import InfiniteScroll from 'react-infinite-scroll-component' +import ProfilePostsListModal from '../ChatItem/profilePosts/ProfileProstsListModal' import usePaginatedMessageIds from '../hooks/usePaginatedMessageIds' import usePinnedMessage from '../hooks/usePinnedMessage' import CenterChatNotice from './CenterChatNotice' @@ -231,6 +232,7 @@ function ChatListContent({ newMessageNoticeClassName={newMessageNoticeClassName} />
+ ) } diff --git a/src/stores/profile-posts-modal.ts b/src/stores/profile-posts-modal.ts new file mode 100644 index 000000000..5e5c8dd5f --- /dev/null +++ b/src/stores/profile-posts-modal.ts @@ -0,0 +1,33 @@ +import { create, createSelectors } from './utils' + +type State = { + isOpen: boolean + chatId?: string + address?: string + hubId?: string + messageId?: string +} + +type Actions = { + closeModal: () => void + openModal: (config?: Omit) => void +} + +const initialState: State = { + isOpen: false, + chatId: undefined, + address: undefined, + hubId: undefined, + messageId: undefined, +} + +const useProfilePostsModalBase = create()((set) => ({ + ...initialState, + openModal: (config) => { + set({ isOpen: true, ...config }) + }, + closeModal: () => { + set(initialState) + }, +})) +export const useProfilePostsModal = createSelectors(useProfilePostsModalBase) From 686f1e3a535b6c74ac7c82b58b7d1330d7413bc0 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 10 Jul 2024 18:48:07 +0700 Subject: [PATCH 26/35] Fix multiple chat menu opening in profile posts --- src/components/chats/ChatItem/ChatItem.tsx | 3 +++ src/components/chats/ChatItem/ChatItemMenus.tsx | 7 +++++-- .../chats/ChatItem/profilePosts/ProfilePostsList.tsx | 1 + src/components/chats/ChatList/ChatItemWithMenu.tsx | 4 ++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/chats/ChatItem/ChatItem.tsx b/src/components/chats/ChatItem/ChatItem.tsx index 627b5fe0b..d57c13daf 100644 --- a/src/components/chats/ChatItem/ChatItem.tsx +++ b/src/components/chats/ChatItem/ChatItem.tsx @@ -23,6 +23,7 @@ export type ChatItemProps = Omit, 'children'> & { hubId: string bg?: 'background-light' | 'background' showApproveButton?: boolean + menuIdPrefix?: string } export default function ChatItem({ @@ -36,6 +37,7 @@ export default function ChatItem({ bg = 'background-light', showApproveButton, enableProfileModal = true, + menuIdPrefix, ...props }: ChatItemProps) { const { ownerId, id: messageId } = message.struct @@ -77,6 +79,7 @@ export default function ChatItem({ )} state.openedChatId === messageId) + const menuId = `${menuIdPrefix || ''}${messageId}` + const isOpen = useChatMenu((state) => state.openedChatId === menuId) const setIsOpenChatMenu = useChatMenu((state) => state.setOpenedChatId) const isMessageOwner = useIsOwnerOfPost(messageId) @@ -344,7 +347,7 @@ export default function ChatItemMenus({ if (closestButton?.classList.contains('superlike') && isOpen) { return } - setIsOpenChatMenu(isOpen ? messageId : null) + setIsOpenChatMenu(isOpen ? menuId : null) }, }} > diff --git a/src/components/chats/ChatItem/profilePosts/ProfilePostsList.tsx b/src/components/chats/ChatItem/profilePosts/ProfilePostsList.tsx index df06535be..3050d3bf6 100644 --- a/src/components/chats/ChatItem/profilePosts/ProfilePostsList.tsx +++ b/src/components/chats/ChatItem/profilePosts/ProfilePostsList.tsx @@ -155,6 +155,7 @@ function ProfilePostsListContent({ message={message} enableProfileModal={false} showBlockedMessage={isAuthorized} + menuIdPrefix='profile-posts-list' /> ) diff --git a/src/components/chats/ChatList/ChatItemWithMenu.tsx b/src/components/chats/ChatList/ChatItemWithMenu.tsx index 69c9b44d4..aaec84476 100644 --- a/src/components/chats/ChatList/ChatItemWithMenu.tsx +++ b/src/components/chats/ChatList/ChatItemWithMenu.tsx @@ -16,6 +16,7 @@ export type ChatItemWithMenuProps = { showBlockedMessage?: boolean scrollToMessage?: ScrollToMessage showApproveButton?: boolean + menuIdPrefix?: string } function InnerChatItemWithMenu({ message, @@ -26,6 +27,7 @@ function InnerChatItemWithMenu({ showBlockedMessage, scrollToMessage, showApproveButton, + menuIdPrefix, }: ChatItemWithMenuProps) { return message ? ( {(config) => { const { referenceProps, toggleDisplay } = config || {} @@ -56,6 +59,7 @@ function InnerChatItemWithMenu({ enableProfileModal={enableProfileModal} scrollToMessage={scrollToMessage} showApproveButton={showApproveButton} + menuIdPrefix={menuIdPrefix} />
) From 916ebe18088ce0e5c3579e599f4f1e290ada6d04 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 10 Jul 2024 18:54:20 +0700 Subject: [PATCH 27/35] Remove offset because it causes bad alignment in mobile (ios) --- src/modules/chat/HomePage/ChatContent.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/chat/HomePage/ChatContent.tsx b/src/modules/chat/HomePage/ChatContent.tsx index 196a188f0..834cda88b 100644 --- a/src/modules/chat/HomePage/ChatContent.tsx +++ b/src/modules/chat/HomePage/ChatContent.tsx @@ -106,7 +106,7 @@ export default function ChatContent({ className }: Props) { Contest Rules ) : ( <> - + Rules )} @@ -366,12 +366,12 @@ function PostMemeButton({ > {!isTimeConstrained ? ( <> - + Post Meme ) : ( <> - {/* */} + {/* */} Posting available in: {countdownText(timeLeft)} )} From c29d5cb56d1f843b233ad7e6b79bf89cfca4ea81 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 10 Jul 2024 19:17:52 +0700 Subject: [PATCH 28/35] Reduce padding --- src/components/chats/ChatRoom/ChatRoom.tsx | 2 +- src/modules/chat/HomePage/ChatContent.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/chats/ChatRoom/ChatRoom.tsx b/src/components/chats/ChatRoom/ChatRoom.tsx index eab37b531..739d3d1ae 100644 --- a/src/components/chats/ChatRoom/ChatRoom.tsx +++ b/src/components/chats/ChatRoom/ChatRoom.tsx @@ -101,7 +101,7 @@ function ChatInputWrapper({ return ( <> - +
+
{isAdmin && ( <> Date: Wed, 10 Jul 2024 20:01:28 +0700 Subject: [PATCH 29/35] Fix issue where non approved memes not added in pending tabs --- src/services/datahub/posts/subscription.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/datahub/posts/subscription.tsx b/src/services/datahub/posts/subscription.tsx index db0899c82..f7122854d 100644 --- a/src/services/datahub/posts/subscription.tsx +++ b/src/services/datahub/posts/subscription.tsx @@ -217,7 +217,7 @@ async function processMessage( myAddress: getMyMainAddress() ?? '', }, (oldData) => { - if (!oldData) return oldData + if (!oldData) return [newestId] const oldIdsSet = new Set(oldData) if (oldIdsSet.has(newestId)) return oldData @@ -231,7 +231,7 @@ async function processMessage( } return false }) - if (index !== -1) { + if (index !== -1 || oldData.length <= 0) { newIds.splice(index, 0, newestId) } @@ -248,7 +248,7 @@ async function processMessage( myAddress: getMyMainAddress() ?? '', }, (oldData) => { - if (!oldData) return oldData + if (!oldData) return [newestId] const oldIdsSet = new Set(oldData) if (oldIdsSet.has(newestId)) return oldData @@ -285,7 +285,7 @@ async function processMessage( } return false }) - if (index !== -1) { + if (index !== -1 || oldData.length <= 0) { newIds.splice(index, 0, newestId) } From 821f69187fbdb5189d5fc97217bedb58953b7bb1 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 10 Jul 2024 20:18:11 +0700 Subject: [PATCH 30/35] Fix issue with image loaded but send button is still disabled --- src/components/extensions/image/ImageModal.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/extensions/image/ImageModal.tsx b/src/components/extensions/image/ImageModal.tsx index 3963c2141..85632b262 100644 --- a/src/components/extensions/image/ImageModal.tsx +++ b/src/components/extensions/image/ImageModal.tsx @@ -15,7 +15,7 @@ import { getTokenomicsMetadataQuery } from '@/services/datahub/content-staking/q import { useExtensionModalState } from '@/stores/extension' import { cx } from '@/utils/class-names' import { resizeImage } from '@/utils/image' -import React, { useEffect, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import Dropzone from 'react-dropzone' import { HiTrash } from 'react-icons/hi2' import { z } from 'zod' @@ -212,9 +212,14 @@ function ImageUpload({ initialImage, setUploadedImageLink }: ImageUploadProps) { saveImage(initialImage) }, [initialImage, saveImage]) + const currentLoadedImage = useRef('') useEffect(() => { const isShowingImage = (!!imageUrl || isLoading) && !isError - setUploadedImageLink({ isShowingImage, loadedLink: null }) + if (currentLoadedImage.current === imageUrl) { + setUploadedImageLink((prev) => ({ ...prev, isShowingImage })) + } else { + setUploadedImageLink({ isShowingImage, loadedLink: null }) + } }, [setUploadedImageLink, imageUrl, isLoading, isError]) if (imageUrl) { @@ -225,6 +230,7 @@ function ImageUpload({ initialImage, setUploadedImageLink }: ImageUploadProps) { onLoad={() => { // To prevent this called first before the useEffect, which causes image rendered, but the link is null setTimeout(() => { + currentLoadedImage.current = imageUrl setUploadedImageLink((prev) => ({ ...prev, loadedLink: imageUrl })) }) }} From 5434cfc698bd2151ef147807bdf76f5826f2314b Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 10 Jul 2024 22:34:04 +0700 Subject: [PATCH 31/35] Add minimum 3 unapproved meme to review --- src/components/chats/UnapprovedMemeCount.tsx | 13 +++ .../extensions/common/CommonChatItem.tsx | 9 +- src/constants/chat.ts | 2 + src/services/datahub/generated-query.ts | 22 ++++- src/services/datahub/posts/mutation.ts | 1 + src/services/datahub/posts/query.ts | 32 ++++++- src/services/datahub/posts/subscription.tsx | 95 ++++++++++++++----- 7 files changed, 148 insertions(+), 26 deletions(-) create mode 100644 src/components/chats/UnapprovedMemeCount.tsx diff --git a/src/components/chats/UnapprovedMemeCount.tsx b/src/components/chats/UnapprovedMemeCount.tsx new file mode 100644 index 000000000..86ef07b01 --- /dev/null +++ b/src/components/chats/UnapprovedMemeCount.tsx @@ -0,0 +1,13 @@ +import { getUnapprovedMemesCountQuery } from '@/services/datahub/posts/query' + +export default function UnapprovedMemeCount({ address }: { address: string }) { + const { data: count, isLoading } = + getUnapprovedMemesCountQuery.useQuery(address) + if (isLoading) return null + + return ( +
+ {(count ?? 0) >= 3 ? '✅' : '🚫'} {count ?? 0} +
+ ) +} diff --git a/src/components/extensions/common/CommonChatItem.tsx b/src/components/extensions/common/CommonChatItem.tsx index ea866f3b0..44fd9b201 100644 --- a/src/components/extensions/common/CommonChatItem.tsx +++ b/src/components/extensions/common/CommonChatItem.tsx @@ -5,6 +5,7 @@ import { useModerateWithSuccessToast } from '@/components/chats/ChatItem/ChatIte import ChatRelativeTime from '@/components/chats/ChatItem/ChatRelativeTime' import MessageStatusIndicator from '@/components/chats/ChatItem/MessageStatusIndicator' import RepliedMessagePreview from '@/components/chats/ChatItem/RepliedMessagePreview' +import UnapprovedMemeCount from '@/components/chats/UnapprovedMemeCount' import { getRepliedMessageId } from '@/components/chats/utils' import SuperLike from '@/components/content-staking/SuperLike' import useAuthorizedForModeration from '@/hooks/useAuthorizedForModeration' @@ -12,12 +13,12 @@ import useIsMessageBlocked from '@/hooks/useIsMessageBlocked' import { getSuperLikeCountQuery } from '@/services/datahub/content-staking/query' import { getModerationReasonsQuery } from '@/services/datahub/moderation/query' import { useApproveUser } from '@/services/datahub/posts/mutation' -import { getProfileQuery } from '@/services/datahub/profiles/query' import { isMessageSent } from '@/services/subsocial/commentIds/optimistic' import { useMyMainAddress } from '@/stores/my-account' import { cx } from '@/utils/class-names' import { getTimeRelativeToNow } from '@/utils/date' import Linkify from 'linkify-react' +import { useInView } from 'react-intersection-observer' import { ExtensionChatItemProps } from '../types' type DerivativesData = { @@ -70,6 +71,7 @@ export default function CommonChatItem({ bg = 'background', showApproveButton, }: CommonChatItemProps) { + const { inView, ref } = useInView() const myAddress = useMyMainAddress() const { isAuthorized } = useAuthorizedForModeration(chatId) const { mutate: moderate, isLoading: loadingModeration } = @@ -202,6 +204,7 @@ export default function CommonChatItem({ 'flex items-baseline gap-2 overflow-hidden px-2.5 first:pt-1.5', othersMessage.checkMark !== 'top' && 'justify-between' )} + ref={ref} > + {showApproveButton && inView && ( + + )} {/* */} {othersMessage.checkMark === 'top' && otherMessageCheckMarkElement()} @@ -351,7 +357,6 @@ function ApproveButton({ chatId: string ownerId: string }) { - const { data: profile } = getProfileQuery.useQuery(ownerId) const { mutate, isLoading } = useApproveUser() return ( ) } diff --git a/src/components/modals/GlobalModals.tsx b/src/components/modals/GlobalModals.tsx index 5e5edb11c..61bdac9ff 100644 --- a/src/components/modals/GlobalModals.tsx +++ b/src/components/modals/GlobalModals.tsx @@ -1,5 +1,6 @@ import { useMessageData } from '@/stores/message' import BlockedModal from '../moderation/BlockedModal' +import MemeOnReviewModal from './MemeOnReviewModal' import PostMemeThresholdModal from './PostMemeThresholdModal' export default function GlobalModals() { @@ -14,6 +15,11 @@ export default function GlobalModals() { isOpen={isOpenMessageModal === 'not-enough-balance'} closeModal={() => setOpenMessageModal('')} /> + setOpenMessageModal('')} + /> setOpenMessageModal('')} diff --git a/src/components/modals/MemeOnReviewModal.tsx b/src/components/modals/MemeOnReviewModal.tsx new file mode 100644 index 000000000..fd546bfe5 --- /dev/null +++ b/src/components/modals/MemeOnReviewModal.tsx @@ -0,0 +1,48 @@ +import Check from '@/assets/emojis/check.png' +import Time from '@/assets/emojis/time.png' +import { MIN_MEME_FOR_REVIEW } from '@/constants/chat' +import { getTokenomicsMetadataQuery } from '@/services/datahub/content-staking/query' +import { getUnapprovedMemesCountQuery } from '@/services/datahub/posts/query' +import { useMyMainAddress } from '@/stores/my-account' +import Image from 'next/image' +import Button from '../Button' +import Modal, { ModalFunctionalityProps } from './Modal' + +export default function MemeOnReviewModal({ + chatId, + ...props +}: ModalFunctionalityProps & { chatId: string }) { + const myAddress = useMyMainAddress() ?? '' + const { data: tokenomics } = getTokenomicsMetadataQuery.useQuery(null) + const { data: count } = getUnapprovedMemesCountQuery.useQuery( + { address: myAddress, chatId }, + { + enabled: props.isOpen, + } + ) + const remaining = MIN_MEME_FOR_REVIEW - (count ?? 0) + + const description = + remaining > 0 + ? `${tokenomics?.socialActionPrice.createCommentPoints} points have been used. We received your meme! We need at least ${remaining} more memes from you to mark you as a verified creator.` + : `${ + tokenomics?.socialActionPrice.createCommentPoints + } points have been used. We received ${ + count ?? 0 + } meme from you! Now we need a bit of time to finish review you as a verified creator.` + + return ( + +
+ 0 ? Time : Check} + alt='' + className='h-28 w-28' + /> + +
+
+ ) +} diff --git a/src/services/datahub/posts/subscription.tsx b/src/services/datahub/posts/subscription.tsx index d177bfcd0..20f876248 100644 --- a/src/services/datahub/posts/subscription.tsx +++ b/src/services/datahub/posts/subscription.tsx @@ -2,10 +2,10 @@ import Toast from '@/components/Toast' import { MIN_MEME_FOR_REVIEW } from '@/constants/chat' import { getPostQuery } from '@/services/api/query' import { commentIdsOptimisticEncoder } from '@/services/subsocial/commentIds/optimistic' +import { useMessageData } from '@/stores/message' import { getMyMainAddress, useMyMainAddress } from '@/stores/my-account' import { useSubscriptionState } from '@/stores/subscription' import { cx } from '@/utils/class-names' -import { PostData } from '@subsocial/api/types' import { QueryClient, useQueryClient } from '@tanstack/react-query' import { gql } from 'graphql-request' import { useEffect, useRef } from 'react' @@ -206,8 +206,8 @@ async function processMessage( } } else { // to not wait for another query to run the other synchronous actions below - processUnapprovedMeme(newPost) - async function processUnapprovedMeme(newPost: PostData) { + processUnapprovedMeme() + async function processUnapprovedMeme() { if (ownerId) { const cachedCount = getUnapprovedMemesCountQuery.getQueryData( queryClient, @@ -235,25 +235,30 @@ async function processMessage( queryClient, { address: myAddress, chatId: rootPostId ?? '' } ) - const remaining = Math.max(MIN_MEME_FOR_REVIEW - (count ?? 0), 0) - const title = - remaining > 0 - ? `Your meme is under review (at least ${remaining} more memes required).` - : `Your meme is under review (${MIN_MEME_FOR_REVIEW} required memes submitted).` - const description = - remaining > 0 - ? `${tokenomics.socialActionPrice.createCommentPoints} points have been used. We received your meme! Hang tight while we give it a quick review. But we need at least ${remaining} memes from you to start the process.` - : `${tokenomics.socialActionPrice.createCommentPoints} points have been used. We received your meme! Hang tight while we give it a quick review.` - toast.custom((t) => ( - ( - - )} - title={title} - description={description} - /> - )) + if (count === 1 || count === 3) { + useMessageData.getState().setOpenMessageModal('on-review') + } else { + const remaining = Math.max(MIN_MEME_FOR_REVIEW - (count ?? 0), 0) + const title = 'Under review' + const description = + remaining > 0 + ? `${tokenomics.socialActionPrice.createCommentPoints} points have been used. We received your meme! We need at least ${remaining} more memes from you to mark you as a verified creator.` + : `${ + tokenomics.socialActionPrice.createCommentPoints + } points have been used. We received ${ + count ?? 0 + } meme from you! Now we need a bit of time to finish review you as a verified creator.` + toast.custom((t) => ( + ( + + )} + title={title} + description={description} + /> + )) + } } } } diff --git a/src/stores/message.ts b/src/stores/message.ts index 45527aef7..04447ba16 100644 --- a/src/stores/message.ts +++ b/src/stores/message.ts @@ -23,7 +23,7 @@ type State = { unreadMessage: UnreadMessage - isOpenMessageModal: 'not-enough-balance' | 'blocked' | '' + isOpenMessageModal: 'not-enough-balance' | 'on-review' | 'blocked' | '' currentChatId: string } From 54b4ae0cfb5c0c119a2f49e9c2f51b3eb8928708 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Thu, 11 Jul 2024 00:07:29 +0700 Subject: [PATCH 34/35] Change check icon to higher res --- src/assets/emojis/check.png | Bin 3748 -> 16416 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/assets/emojis/check.png b/src/assets/emojis/check.png index f4ff0a401c9a3ada4f7af669123255407e03590e..f47b42376ce413e8856e12ccf7cbed64d115658e 100644 GIT binary patch literal 16416 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 Date: Thu, 11 Jul 2024 00:17:54 +0700 Subject: [PATCH 35/35] Update wording --- src/components/modals/MemeOnReviewModal.tsx | 8 ++++++-- src/services/datahub/posts/subscription.tsx | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/modals/MemeOnReviewModal.tsx b/src/components/modals/MemeOnReviewModal.tsx index fd546bfe5..536ff2dfb 100644 --- a/src/components/modals/MemeOnReviewModal.tsx +++ b/src/components/modals/MemeOnReviewModal.tsx @@ -24,12 +24,16 @@ export default function MemeOnReviewModal({ const description = remaining > 0 - ? `${tokenomics?.socialActionPrice.createCommentPoints} points have been used. We received your meme! We need at least ${remaining} more memes from you to mark you as a verified creator.` + ? `${ + tokenomics?.socialActionPrice.createCommentPoints + } points have been used. We received your meme! We need at least ${remaining} more meme${ + remaining > 1 ? 's' : '' + } from you to mark you as a verified creator.` : `${ tokenomics?.socialActionPrice.createCommentPoints } points have been used. We received ${ count ?? 0 - } meme from you! Now we need a bit of time to finish review you as a verified creator.` + } memes from you! Now we need a bit of time to finish review you as a verified creator.` return ( diff --git a/src/services/datahub/posts/subscription.tsx b/src/services/datahub/posts/subscription.tsx index 20f876248..320394606 100644 --- a/src/services/datahub/posts/subscription.tsx +++ b/src/services/datahub/posts/subscription.tsx @@ -242,12 +242,16 @@ async function processMessage( const title = 'Under review' const description = remaining > 0 - ? `${tokenomics.socialActionPrice.createCommentPoints} points have been used. We received your meme! We need at least ${remaining} more memes from you to mark you as a verified creator.` + ? `${ + tokenomics.socialActionPrice.createCommentPoints + } points have been used. We received your meme! We need at least ${remaining} more meme${ + remaining > 1 ? 's' : '' + } from you to mark you as a verified creator.` : `${ tokenomics.socialActionPrice.createCommentPoints } points have been used. We received ${ count ?? 0 - } meme from you! Now we need a bit of time to finish review you as a verified creator.` + } memes from you! Now we need a bit of time to finish review you as a verified creator.` toast.custom((t) => (