From b08fadc2cd5b02bf1e38098f1a412e3f6b231871 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Mon, 15 Jul 2024 23:53:40 +0700 Subject: [PATCH 01/12] Disable retry on healthcheck query --- src/pages/_app.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 40515b99b..5801227a5 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -269,6 +269,7 @@ function SessionAccountChecker() { function DatahubHealthChecker() { const { data } = getDatahubHealthQuery.useQuery(null, { refetchInterval: 10_000, + retry: false, }) const currentId = useRef('') useEffect(() => { From 1aa01115a430e8de960f1cd457ff9f7e77b74a38 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 16 Jul 2024 17:21:23 +0300 Subject: [PATCH 02/12] Add check in task link to points widget --- src/components/layouts/MobileNavigation.tsx | 9 +- src/components/referral/CustomLink.tsx | 16 ++- src/modules/points/CheckInTaskPreview.tsx | 57 +++++++++ src/modules/points/PointsWidget.tsx | 4 +- .../telegram/TapPage/PointsClicker.tsx | 113 ++++++++++-------- src/modules/telegram/TapPage/index.tsx | 23 +++- 6 files changed, 165 insertions(+), 57 deletions(-) create mode 100644 src/modules/points/CheckInTaskPreview.tsx diff --git a/src/components/layouts/MobileNavigation.tsx b/src/components/layouts/MobileNavigation.tsx index a80f14b13..14f2f5e20 100644 --- a/src/components/layouts/MobileNavigation.tsx +++ b/src/components/layouts/MobileNavigation.tsx @@ -195,8 +195,13 @@ function NewMemeNotice() { return null } -const RedDot = () => ( -
+export const RedDot = ({ className }: { className?: string }) => ( +
) export default MobileNavigation diff --git a/src/components/referral/CustomLink.tsx b/src/components/referral/CustomLink.tsx index 8ea77ec1e..6c8338b43 100644 --- a/src/components/referral/CustomLink.tsx +++ b/src/components/referral/CustomLink.tsx @@ -1,4 +1,5 @@ import Link, { LinkProps } from 'next/link' +import { useRouter } from 'next/router' import urlJoin from 'url-join' import { useReferralId } from './ReferralUrlChanger' @@ -14,6 +15,7 @@ export default function ProfileLinkCustomLink({ }) { const refId = useReferralId() const { href, as } = props + const { pathname } = useRouter() if (!href) { return @@ -31,7 +33,19 @@ export default function ProfileLinkCustomLink({ return } - return + return ( + { + if (pathname === href) { + e.stopPropagation() + e.preventDefault() + } + props.onClick?.(e) + }} + href={href} + /> + ) } function augmentLink(link: LinkProps['href'], refId: string) { diff --git a/src/modules/points/CheckInTaskPreview.tsx b/src/modules/points/CheckInTaskPreview.tsx new file mode 100644 index 000000000..d3157715a --- /dev/null +++ b/src/modules/points/CheckInTaskPreview.tsx @@ -0,0 +1,57 @@ +import CalendarImage from '@/assets/graphics/tasks/calendar.png' +import { RedDot } from '@/components/layouts/MobileNavigation' +import SkeletonFallback from '@/components/SkeletonFallback' +import { getServerDayQuery } from '@/services/api/query' +import { getDailyRewardQuery } from '@/services/datahub/content-staking/query' +import { useMyMainAddress } from '@/stores/my-account' +import { cx } from '@/utils/class-names' +import Image from 'next/image' +import CustomLink from 'src/components/referral/CustomLink' + +const CheckInTaskPreview = () => { + const myAddress = useMyMainAddress() + + const { data: serverDay, isLoading: loadingServerDay } = + getServerDayQuery.useQuery(null) + const { data: dailyReward, isLoading: loadingDailyReward } = + getDailyRewardQuery.useQuery(myAddress ?? '') + const todayRewardIndex = dailyReward?.claims.findIndex( + (claim) => Number(claim.claimValidDay) === serverDay?.day + ) + + const todayReward = dailyReward?.claims[todayRewardIndex || 0] + const isTodayRewardClaimed = !!todayReward && !todayReward.openToClaim + + return ( + { + e.stopPropagation() + }} + > +
+ + + {!isTodayRewardClaimed && ( + + )} + + + + {(todayRewardIndex || 0) + 1} + + /7 + +
+
+ ) +} + +export default CheckInTaskPreview diff --git a/src/modules/points/PointsWidget.tsx b/src/modules/points/PointsWidget.tsx index 8481168c7..6775479a8 100644 --- a/src/modules/points/PointsWidget.tsx +++ b/src/modules/points/PointsWidget.tsx @@ -36,6 +36,7 @@ import { MdContentCopy } from 'react-icons/md' import { RiPencilFill } from 'react-icons/ri' import { toast } from 'sonner' import { LeaderboardContent } from '../telegram/StatsPage/LeaderboardSection' +import CheckInTaskPreview from './CheckInTaskPreview' import LikeCount from './LikePreview' import Points from './PointsPreview' @@ -84,10 +85,11 @@ export default function PointsWidget({ /10
+
- +
diff --git a/src/modules/telegram/TapPage/PointsClicker.tsx b/src/modules/telegram/TapPage/PointsClicker.tsx index 024899b9e..8e4fe6cc4 100644 --- a/src/modules/telegram/TapPage/PointsClicker.tsx +++ b/src/modules/telegram/TapPage/PointsClicker.tsx @@ -22,7 +22,8 @@ import BN from 'bignumber.js' import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' import Link from 'next/link' -import { TouchEvent, TouchList, useEffect, useRef, useState } from 'react' +import { TouchEvent, TouchList, memo, useEffect, useRef, useState } from 'react' +import { createPortal } from 'react-dom' import { HiXMark } from 'react-icons/hi2' import { getEnergyStateStore, @@ -230,7 +231,7 @@ const PointsClicker = ({ className }: PointsClickerProps) => { }} />
- @@ -247,11 +248,16 @@ const LikeMemesInfoMessage = ({ showMemesInfoMessage, setShowMemesInfoMessage, }: LikeMemesInfoMessageProps) => { + const [domReady, setDomReady] = useState(false) const { data: tokenomicMetadata, isLoading: isTokenomicMetadataLoading } = getActiveStakingTokenomicMetadataQuery.useQuery({}) const sendEvent = useSendEvent() + useEffect(() => { + setDomReady(true) + }, []) + if (!showMemesInfoMessage) return null const userLikeWeight = @@ -266,59 +272,64 @@ const LikeMemesInfoMessage = ({ .toString() : '0' - return ( -
- { - const { day } = getDayAndWeekTimestamp() - - likeMemesInfoMessageStorage.set(day.toString()) - sendEvent('tooltip_like_memes_clicked') - }} - > - 💡 -
- - Like memes and earn more - - - Each like on a meme brings{' '} - { - - }{' '} - points - -
-
- -
- -
- ) + 💡 +
+ + Like memes and earn more + + + Each like on a meme brings{' '} + { + + }{' '} + points + +
+
+ +
+ + , + document.getElementById('tap-page-container')! + ) + : null } +const LikeMemesInfoMessageMemo = memo(LikeMemesInfoMessage) + export default PointsClicker diff --git a/src/modules/telegram/TapPage/index.tsx b/src/modules/telegram/TapPage/index.tsx index e1779be22..008fae3a3 100644 --- a/src/modules/telegram/TapPage/index.tsx +++ b/src/modules/telegram/TapPage/index.tsx @@ -1,7 +1,9 @@ +import Diamond from '@/assets/emojis/diamond.png' import TapFromMobileImage from '@/assets/graphics/tap-from-mobile.png' import SkeletonFallback from '@/components/SkeletonFallback' import LayoutWithBottomNavigation from '@/components/layouts/LayoutWithBottomNavigation' import useTgNoScroll from '@/hooks/useTgNoScroll' +import Points from '@/modules/points/PointsPreview' import PointsWidget from '@/modules/points/PointsWidget' import { FULL_ENERGY_VALUE, @@ -40,13 +42,30 @@ const TapPageContent = () => { } return ( -
- +
+
+ + +
) } +const PointsPreview = () => { + return ( +
+ + + + +
+ ) +} + const EnergyState = () => { const myAddress = useMyMainAddress() From ca4a2f16d3ad7eb09d9f536b1bd9de4546b8da95 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 17 Jul 2024 15:05:54 +0300 Subject: [PATCH 03/12] Add search user posts for moderators --- .../profilePosts/ProfileProstsListModal.tsx | 70 ++++++++- src/modules/chat/HomePage/ChatContent.tsx | 132 +---------------- src/modules/chat/HomePage/ChatTabs.tsx | 135 ++++++++++++++++++ .../telegram/AirdropPage/SearchUser.tsx | 77 ++++++++++ src/modules/telegram/AirdropPage/index.tsx | 2 + 5 files changed, 283 insertions(+), 133 deletions(-) create mode 100644 src/modules/chat/HomePage/ChatTabs.tsx create mode 100644 src/modules/telegram/AirdropPage/SearchUser.tsx diff --git a/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx b/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx index 292f3b89c..6cc4e9dd9 100644 --- a/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx +++ b/src/components/chats/ChatItem/profilePosts/ProfileProstsListModal.tsx @@ -1,27 +1,52 @@ import AddressAvatar from '@/components/AddressAvatar' import Button from '@/components/Button' import Name from '@/components/Name' +import { env } from '@/env.mjs' import useAuthorizedForModeration from '@/hooks/useAuthorizedForModeration' +import { TabButton } from '@/modules/chat/HomePage/ChatTabs' 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 { useRouter } from 'next/router' +import { useEffect, 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' -const ProfilePostsListModal = () => { +type Tab = 'all' | 'contest' + +type ProfilePostsListModalProps = { + tabsConfig?: { + defaultTab: Tab + } +} + +const defaultHubId = env.NEXT_PUBLIC_MAIN_SPACE_ID +const chatIdByTab = { + all: env.NEXT_PUBLIC_MAIN_CHAT_ID, + contest: env.NEXT_PUBLIC_CONTEST_CHAT_ID, +} + +const ProfilePostsListModal = ({ tabsConfig }: ProfilePostsListModalProps) => { + const [selectedTab, setSelectedTab] = useState( + tabsConfig?.defaultTab || 'all' + ) + + const router = useRouter() + const { isOpen, closeModal, messageId = '', - chatId = '', - hubId = '', + chatId = tabsConfig ? chatIdByTab[tabsConfig.defaultTab] : '', + hubId = tabsConfig ? defaultHubId : '', address = '', + openModal, } = useProfilePostsModal() const { mutate: moderate } = useModerateWithSuccessToast(messageId, chatId) @@ -52,6 +77,10 @@ const ProfilePostsListModal = () => { closeModal() } + useEffect(() => { + closeModal() + }, [closeModal, router.asPath]) + return createPortal( <> { )}
+ {tabsConfig && ( +
+ { + setSelectedTab(tab as any) + openModal({ + address, + chatId: chatIdByTab[tab as Tab], + hubId: defaultHubId, + }) + }} + size={'md'} + > + All memes + + { + setSelectedTab(tab as any) + openModal({ + address, + chatId: chatIdByTab[tab as Tab], + hubId: defaultHubId, + }) + }} + size={'md'} + > + Contest + +
+ )}
diff --git a/src/modules/chat/HomePage/ChatContent.tsx b/src/modules/chat/HomePage/ChatContent.tsx index 016a88923..95023df48 100644 --- a/src/modules/chat/HomePage/ChatContent.tsx +++ b/src/modules/chat/HomePage/ChatContent.tsx @@ -2,7 +2,6 @@ import Shield from '@/assets/icons/shield.svg' import Button from '@/components/Button' import LinkText from '@/components/LinkText' import Notice from '@/components/Notice' -import { Skeleton } from '@/components/SkeletonFallback' import ChatRoom from '@/components/chats/ChatRoom' import LinkEvmAddressModal from '@/components/modals/LinkEvmAddressModal' import Meme2EarnIntroModal, { @@ -11,7 +10,6 @@ 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' @@ -22,13 +20,13 @@ import { useSendEvent } from '@/stores/analytics' import { useExtensionData } from '@/stores/extension' import { useMessageData } from '@/stores/message' import { useMyMainAddress } from '@/stores/my-account' -import { cx } from '@/utils/class-names' import { useLocalStorage } from '@uidotdev/usehooks' import dayjs from 'dayjs' import duration from 'dayjs/plugin/duration' import Router, { useRouter } from 'next/router' -import { ReactNode, useEffect, useLayoutEffect, useState } from 'react' +import { useEffect, useLayoutEffect, useState } from 'react' import { LuPlusCircle } from 'react-icons/lu' +import { TabState, Tabs, tabStates } from './ChatTabs' dayjs.extend(duration) @@ -126,132 +124,6 @@ export default function ChatContent({ className }: Props) { ) } -const tabStates = [ - 'all', - 'contest', - 'not-approved', - 'not-approved-contest', -] as const -type TabState = (typeof tabStates)[number] -function TabButton({ - selectedTab, - setSelectedTab, - tab, - children, - className, - size = 'md', -}: { - tab: TabState - selectedTab: TabState - setSelectedTab: (tab: TabState) => void - children: ReactNode - className?: string - size?: 'md' | 'sm' -}) { - const isSelected = selectedTab === tab - return ( - - ) -} - -function Tabs({ - setSelectedTab, - selectedTab, -}: { - 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 - - - )} - - {isAdmin ? 'Approved' : 'All memes'} - - - {!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 - )} - -
- ) -} - function countdownText(timeLeft: number) { const timeDuration = dayjs.duration({ milliseconds: timeLeft }) const minutes = Math.floor(timeDuration.asMinutes()) diff --git a/src/modules/chat/HomePage/ChatTabs.tsx b/src/modules/chat/HomePage/ChatTabs.tsx new file mode 100644 index 000000000..573e4db52 --- /dev/null +++ b/src/modules/chat/HomePage/ChatTabs.tsx @@ -0,0 +1,135 @@ +import Button from '@/components/Button' +import { Skeleton } from '@/components/SkeletonFallback' +import { env } from '@/env.mjs' +import useIsModerationAdmin from '@/hooks/useIsModerationAdmin' +import { getServerTimeQuery } from '@/services/api/query' +import { cx } from '@/utils/class-names' +import dayjs from 'dayjs' +import { ReactNode } from 'react' + +export const tabStates = [ + 'all', + 'contest', + 'not-approved', + 'not-approved-contest', +] as const + +export type TabState = (typeof tabStates)[number] +export function TabButton({ + selectedTab, + setSelectedTab, + tab, + children, + className, + size = 'md', +}: { + tab: TabState + selectedTab: TabState + setSelectedTab: (tab: TabState) => void + children: ReactNode + className?: string + size?: 'md' | 'sm' +}) { + const isSelected = selectedTab === tab + return ( + + ) +} + +export function Tabs({ + setSelectedTab, + selectedTab, +}: { + 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 + + + )} + + {isAdmin ? 'Approved' : 'All memes'} + + + {!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 + )} + +
+ ) +} diff --git a/src/modules/telegram/AirdropPage/SearchUser.tsx b/src/modules/telegram/AirdropPage/SearchUser.tsx new file mode 100644 index 000000000..49ac5eba9 --- /dev/null +++ b/src/modules/telegram/AirdropPage/SearchUser.tsx @@ -0,0 +1,77 @@ +import Button from '@/components/Button' +import ProfilePostsListModal from '@/components/chats/ChatItem/profilePosts/ProfileProstsListModal' +import Input from '@/components/inputs/Input' +import Toast from '@/components/Toast' +import { useProfilePostsModal } from '@/stores/profile-posts-modal' +import { cx, mutedTextColorStyles } from '@/utils/class-names' +import { isEthereumAddress } from '@polkadot/util-crypto' +import { useState } from 'react' +import { IoSearchSharp } from 'react-icons/io5' +import { toast } from 'sonner' + +function getStartParam(urlOrAddress: string) { + let result = '' + + try { + let urlObj = new URL(urlOrAddress) + + let startParam = urlObj.searchParams.get('start') + + result = startParam || '' + } catch { + result = urlOrAddress + } + + const isEth = isEthereumAddress(result) + + return { + address: isEth ? result : undefined, + error: !isEth ? 'Address is not valid' : undefined, + } +} + +const SearchUser = () => { + const [value, setValue] = useState('') + const { openModal } = useProfilePostsModal() + + return ( + <> +
+ Search user posts: +
+ { + setValue(e.target.value) + }} + /> + + +
+
+ + + ) +} + +export default SearchUser diff --git a/src/modules/telegram/AirdropPage/index.tsx b/src/modules/telegram/AirdropPage/index.tsx index cb1237b36..c9739f63d 100644 --- a/src/modules/telegram/AirdropPage/index.tsx +++ b/src/modules/telegram/AirdropPage/index.tsx @@ -23,6 +23,7 @@ import { MdContentCopy } from 'react-icons/md' import { RiPencilFill } from 'react-icons/ri' import { toast } from 'sonner' import RemoveLinkedIdentityModal from './RemoveLinkedIdentityModal' +import SearchUser from './SearchUser' export default function AirdropPage() { useTgNoScroll() @@ -51,6 +52,7 @@ export default function AirdropPage() { + {isAdmin && }
From 82baefed9e48924015dffe642e76bcd47e7ff0ce Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 17 Jul 2024 15:06:30 +0300 Subject: [PATCH 04/12] Remove console log --- src/modules/telegram/AirdropPage/SearchUser.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/telegram/AirdropPage/SearchUser.tsx b/src/modules/telegram/AirdropPage/SearchUser.tsx index 49ac5eba9..0030e7c2b 100644 --- a/src/modules/telegram/AirdropPage/SearchUser.tsx +++ b/src/modules/telegram/AirdropPage/SearchUser.tsx @@ -54,7 +54,6 @@ const SearchUser = () => { disabled={!value} onClick={() => { const { address, error } = getStartParam(value) - console.log(address) if (error) { toast.custom((t) => , { From f7c3632f2194aa4899874db101b3116a4f4643a5 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Thu, 18 Jul 2024 16:58:12 +0700 Subject: [PATCH 05/12] Revert "Fix wrong code from the last order change" This reverts commit e17d6369f7a09b399fb93fcdf74f81dc41f5c548. --- src/services/datahub/posts/subscription.tsx | 87 +++++++++++++-------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/src/services/datahub/posts/subscription.tsx b/src/services/datahub/posts/subscription.tsx index a3fdf8d16..6ace0a977 100644 --- a/src/services/datahub/posts/subscription.tsx +++ b/src/services/datahub/posts/subscription.tsx @@ -284,7 +284,6 @@ async function processMessage( if (!rootPostId) return const isApproved = entity.approvedInRootPost - // To show the user's unapproved message in approved tab if (isCreationEvent && !isApproved && isCurrentOwner) { getPaginatedPostIdsByPostId.setQueryFirstPageData( queryClient, @@ -297,6 +296,7 @@ async function processMessage( if (!oldData) return [newestId] 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) @@ -310,49 +310,72 @@ async function processMessage( if (index !== -1 || oldData.length <= 0) { newIds.splice(index, 0, newestId) } + return newIds } ) } - getPaginatedPostIdsByPostId.setQueryFirstPageData( - queryClient, - { - postId: rootPostId, - onlyDisplayUnapprovedMessages: !isApproved, - myAddress: getMyMainAddress() ?? '', - }, - (oldData) => { - if (!oldData) return [newestId] - const oldIdsSet = new Set(oldData) - if (oldIdsSet.has(newestId)) return oldData + if (isApproved) { + getPaginatedPostIdsByPostId.setQueryFirstPageData( + queryClient, + { + postId: rootPostId, + onlyDisplayUnapprovedMessages: !isApproved, + myAddress: getMyMainAddress() ?? '', + }, + (oldData) => { + if (!oldData) return [newestId] + const oldIdsSet = new Set(oldData) + if (oldIdsSet.has(newestId)) return oldData - const newIds = [...oldData] + const newIds = [...oldData] - const usedAsClientOptimisticId = entity.optimisticId || entity.id - const clientOptimisticId = commentIdsOptimisticEncoder.encode( - usedAsClientOptimisticId ?? '' - ) - if (oldIdsSet.has(clientOptimisticId)) { - const optimisticIdIndex = newIds.findIndex( - (id) => id === clientOptimisticId + const usedAsClientOptimisticId = entity.optimisticId || entity.id + const clientOptimisticId = commentIdsOptimisticEncoder.encode( + usedAsClientOptimisticId ?? '' ) - newIds.splice(optimisticIdIndex, 1, newestId) - return newIds - } + if (oldIdsSet.has(clientOptimisticId)) { + const optimisticIdIndex = newIds.findIndex( + (id) => id === clientOptimisticId + ) + newIds.splice(optimisticIdIndex, 1, newestId) + return newIds + } - if (entity.persistentId && oldIdsSet.has(entity.id)) { - const optimisticIdIndex = newIds.findIndex((id) => id === entity.id) - newIds.splice(optimisticIdIndex, 1, newestId) + if (entity.persistentId && oldIdsSet.has(entity.id)) { + const optimisticIdIndex = newIds.findIndex((id) => id === entity.id) + newIds.splice(optimisticIdIndex, 1, newestId) - return newIds - } + return newIds + } - newIds.unshift(newestId) + const index = oldData.findIndex((id) => { + const data = getPostQuery.getQueryData(queryClient, id) + if (!data) return false + if ( + new Date(data.struct.createdAtTime) <= + new Date(eventData.entity.createdAtTime) + ) { + newIds.unshift(newestId) + return true + } + return false + }) + if (index !== -1 || oldData.length <= 0) { + newIds.splice(index, 0, newestId) + } - return newIds - } - ) + return newIds + } + ) + } else { + getPaginatedPostIdsByPostId.invalidateLastQuery(queryClient, { + postId: rootPostId, + onlyDisplayUnapprovedMessages: !isApproved, + myAddress: getMyMainAddress() ?? '', + }) + } getPostMetadataQuery.invalidate(queryClient, rootPostId) } From b76b923488d084138edc4146656006ea7f0cb58d Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Thu, 18 Jul 2024 17:08:23 +0700 Subject: [PATCH 06/12] Change order to oldest -> newest with new moderation filter --- src/services/datahub/posts/query.ts | 9 ++++--- src/services/datahub/posts/subscription.tsx | 30 ++------------------- 2 files changed, 8 insertions(+), 31 deletions(-) diff --git a/src/services/datahub/posts/query.ts b/src/services/datahub/posts/query.ts index 8bff21728..90348913e 100644 --- a/src/services/datahub/posts/query.ts +++ b/src/services/datahub/posts/query.ts @@ -107,14 +107,17 @@ async function getPaginatedPostIdsByRootPostId({ }, ], } - : { + : ({ rootPostId: postId, approvedInRootPost: !onlyDisplayUnapprovedMessages, - }, + blockedByModeration: false, + } as any), orderBy: onlyDisplayUnapprovedMessages ? 'createdAtTime' : 'approvedInRootPostAtTime', - orderDirection: QueryOrder.Desc, + orderDirection: onlyDisplayUnapprovedMessages + ? QueryOrder.Asc + : QueryOrder.Desc, pageSize: CHAT_PER_PAGE, offset, }, diff --git a/src/services/datahub/posts/subscription.tsx b/src/services/datahub/posts/subscription.tsx index 6ace0a977..5a0321046 100644 --- a/src/services/datahub/posts/subscription.tsx +++ b/src/services/datahub/posts/subscription.tsx @@ -298,18 +298,7 @@ async function processMessage( 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 || oldData.length <= 0) { - newIds.splice(index, 0, newestId) - } + newIds.unshift(newestId) return newIds } @@ -350,22 +339,7 @@ async function processMessage( return newIds } - const index = oldData.findIndex((id) => { - const data = getPostQuery.getQueryData(queryClient, id) - if (!data) return false - if ( - new Date(data.struct.createdAtTime) <= - new Date(eventData.entity.createdAtTime) - ) { - newIds.unshift(newestId) - return true - } - return false - }) - if (index !== -1 || oldData.length <= 0) { - newIds.splice(index, 0, newestId) - } - + newIds.unshift(newestId) return newIds } ) From 23a110eb05f24d2bb0fbe07361d79c8791ce8c7f Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Thu, 18 Jul 2024 17:31:20 +0700 Subject: [PATCH 07/12] Update types --- src/services/datahub/generated-query.ts | 2 ++ src/services/datahub/posts/query.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/datahub/generated-query.ts b/src/services/datahub/generated-query.ts index 6eb441d0d..5b525c935 100644 --- a/src/services/datahub/generated-query.ts +++ b/src/services/datahub/generated-query.ts @@ -498,6 +498,8 @@ export type FindPostsFilter = { OR?: InputMaybe> activeStaking?: InputMaybe approvedInRootPost?: InputMaybe + /** Do not provide this parameter if you do not need include moderation filter in response. */ + blockedByModeration?: InputMaybe createdAtTime?: InputMaybe /** Datetime as ISO 8601 string */ createdAtTimeGt?: InputMaybe diff --git a/src/services/datahub/posts/query.ts b/src/services/datahub/posts/query.ts index 90348913e..21d232a97 100644 --- a/src/services/datahub/posts/query.ts +++ b/src/services/datahub/posts/query.ts @@ -107,11 +107,11 @@ async function getPaginatedPostIdsByRootPostId({ }, ], } - : ({ + : { rootPostId: postId, approvedInRootPost: !onlyDisplayUnapprovedMessages, blockedByModeration: false, - } as any), + }, orderBy: onlyDisplayUnapprovedMessages ? 'createdAtTime' : 'approvedInRootPostAtTime', From 7abe4c506e65b946d81ff95a956ba0c143e6736e Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Thu, 18 Jul 2024 18:03:21 +0700 Subject: [PATCH 08/12] Disable approve and super like button in ended contest --- src/components/content-staking/SuperLike.tsx | 12 +++++++++++- src/components/extensions/common/CommonChatItem.tsx | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/components/content-staking/SuperLike.tsx b/src/components/content-staking/SuperLike.tsx index 415a0e4ab..d2cd17611 100644 --- a/src/components/content-staking/SuperLike.tsx +++ b/src/components/content-staking/SuperLike.tsx @@ -1,4 +1,5 @@ import Thumbsup from '@/assets/emojis/thumbsup.png' +import { env } from '@/env.mjs' import { useIsAddressBlockedInApp } from '@/hooks/useIsAddressBlockedInApp' import { getPostQuery, getServerTimeQuery } from '@/services/api/query' import { useCreateSuperLike } from '@/services/datahub/content-staking/mutation' @@ -63,6 +64,13 @@ export function SuperLikeWrapper({ const { mutate: createSuperLike } = useCreateSuperLike() const { data: superLikeCount } = getSuperLikeCountQuery.useQuery(postId) + const isInContest = + post?.struct.rootPostId === env.NEXT_PUBLIC_CONTEST_CHAT_ID + const isContestEnded = dayjs().isAfter( + dayjs(env.NEXT_PUBLIC_CONTEST_END_TIME) + ) + const isInEndedContest = isInContest && isContestEnded + const { canBeLiked: canBeSuperliked, isLoading: loadingCanBeLiked } = useClientValidationOfPostSuperLike(post?.struct.createdAtTime ?? 0) @@ -94,7 +102,8 @@ export function SuperLikeWrapper({ loadingTodayCount || loadingCanBeLiked || hasLikedMoreThanLimit || - !message) && + !message || + isInEndedContest) && !hasILiked let disabledCause = '' @@ -106,6 +115,7 @@ export function SuperLikeWrapper({ disabledCause = `You've liked 10 ${entity} today. Come back tomorrow for more fun!` else if (!canBeSuperliked) disabledCause = `You cannot like ${entity}s that are older than 7 days` + else if (isInEndedContest) disabledCause = `Contest has ended` const handleClick = async (e?: React.MouseEvent) => { // prevent chat menu from opening when clicking this button diff --git a/src/components/extensions/common/CommonChatItem.tsx b/src/components/extensions/common/CommonChatItem.tsx index f12371139..6635567aa 100644 --- a/src/components/extensions/common/CommonChatItem.tsx +++ b/src/components/extensions/common/CommonChatItem.tsx @@ -11,6 +11,7 @@ import { getRepliedMessageId } from '@/components/chats/utils' import SuperLike, { SuperLikeButton, } from '@/components/content-staking/SuperLike' +import { env } from '@/env.mjs' import useAuthorizedForModeration from '@/hooks/useAuthorizedForModeration' import useIsMessageBlocked from '@/hooks/useIsMessageBlocked' import useIsModerationAdmin from '@/hooks/useIsModerationAdmin' @@ -24,6 +25,7 @@ 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 dayjs from 'dayjs' import Linkify from 'linkify-react' import { useInView } from 'react-intersection-observer' import { ExtensionChatItemProps } from '../types' @@ -428,11 +430,16 @@ function ApproveUserButton({ function ApproveMemeButton({ messageId, + chatId, }: { chatId: string messageId: string }) { const { mutate, isLoading } = useApproveMessage() + const isInContest = chatId === env.NEXT_PUBLIC_CONTEST_CHAT_ID + const isContestEnded = dayjs().isAfter(env.NEXT_PUBLIC_CONTEST_END_TIME) + const isInEndedContest = isInContest && isContestEnded + return (