diff --git a/src/components/utils/datahub-queue/super-likes.ts b/src/components/utils/datahub-queue/super-likes.ts deleted file mode 100644 index 47fa0835d..000000000 --- a/src/components/utils/datahub-queue/super-likes.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SocialCallDataArgs, socialCallName } from '@subsocial/data-hub-sdk' -import axios from 'axios' -import { createSocialDataEventPayload, DatahubParams } from './utils' - -export async function createSuperLike( - params: DatahubParams>, -) { - const input = createSocialDataEventPayload( - socialCallName.synth_active_staking_create_super_like, - params, - ) - - const res = await axios.post('/api/datahub/super-likes', input) - return res.data -} diff --git a/src/components/utils/datahub/super-likes.ts b/src/components/utils/datahub/super-likes.ts new file mode 100644 index 000000000..e92e15f41 --- /dev/null +++ b/src/components/utils/datahub/super-likes.ts @@ -0,0 +1,43 @@ +import { SocialCallDataArgs, socialCallName } from '@subsocial/data-hub-sdk' +import axios from 'axios' +import { gql } from 'graphql-request' +import { createSocialDataEventPayload, DatahubParams, datahubQueryRequest } from './utils' + +// QUERIES +const GET_SUPER_LIKE_COUNTS = gql` + query GetSuperLikeCounts($postIds: [String!]!) { + activeStakingSuperLikeCountsByPost(args: { postPersistentIds: $postIds }) { + persistentPostId + count + } + } +` +export async function getSuperLikeCounts(postIds: string[]) { + const res = (await datahubQueryRequest({ + document: GET_SUPER_LIKE_COUNTS, + variables: { postIds }, + })) as { + activeStakingSuperLikeCountsByPost: { + persistentPostId: string + count: number + }[] + } + + return res.activeStakingSuperLikeCountsByPost.map(item => ({ + postId: item.persistentPostId, + count: item.count, + })) +} + +// MUTATIONS +export async function createSuperLike( + params: DatahubParams>, +) { + const input = createSocialDataEventPayload( + socialCallName.synth_active_staking_create_super_like, + params, + ) + + const res = await axios.post('/api/datahub/super-likes', input) + return res.data +} diff --git a/src/components/utils/datahub-queue/utils.ts b/src/components/utils/datahub/utils.ts similarity index 60% rename from src/components/utils/datahub-queue/utils.ts rename to src/components/utils/datahub/utils.ts index 54f755f96..2cfa63317 100644 --- a/src/components/utils/datahub-queue/utils.ts +++ b/src/components/utils/datahub/utils.ts @@ -5,7 +5,25 @@ import { SocialEventDataType, socialEventProtVersion, } from '@subsocial/data-hub-sdk' +import { GraphQLClient, RequestOptions, Variables } from 'graphql-request' +import { datahubQueryUrl } from 'src/config/env' +// QUERIES +export function datahubQueryRequest( + config: RequestOptions, +) { + if (!datahubQueryUrl) throw new Error('Datahub (Query) config is not set') + + const TIMEOUT = 10 * 1000 // 10 seconds + const client = new GraphQLClient(datahubQueryUrl, { + timeout: TIMEOUT, + ...config, + }) + + return client.request({ url: datahubQueryUrl, ...config }) +} + +// MUTATIONS export type DatahubParams = { address: string diff --git a/src/components/voting/SuperLike.tsx b/src/components/voting/SuperLike.tsx index 52356d084..83d3303c3 100644 --- a/src/components/voting/SuperLike.tsx +++ b/src/components/voting/SuperLike.tsx @@ -3,19 +3,21 @@ import { Button, ButtonProps } from 'antd' import clsx from 'clsx' import { CSSProperties } from 'react' import { AiFillHeart, AiOutlineHeart } from 'react-icons/ai' +import { useSuperLikeCount } from 'src/rtk/features/activeStaking/superLikeCountsHooks' import { useOpenCloseOnBoardingModal } from 'src/rtk/features/onBoarding/onBoardingHooks' import { useAuth } from '../auth/AuthContext' import { useMyAddress } from '../auth/MyAccountsContext' import { IconWithLabel } from '../utils' -import { createSuperLike } from '../utils/datahub-queue/super-likes' +import { createSuperLike } from '../utils/datahub/super-likes' export type SuperLikeProps = ButtonProps & { post: PostStruct } export default function SuperLike({ post, ...props }: SuperLikeProps) { + const count = useSuperLikeCount(post.id) const isActive = true - const count = 21 + const openOnBoardingModal = useOpenCloseOnBoardingModal() const myAddress = useMyAddress() diff --git a/src/config/env.ts b/src/config/env.ts index 572a2de2e..8a61a33fd 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -33,6 +33,8 @@ export const datahubQueueConfig = { token: process.env['DATAHUB_QUEUE_TOKEN'], } +export const datahubQueryUrl = process.env['NEXT_PUBLIC_DATAHUB_QUERY_URL'] + /** * Enable or disable the available features of this web app by overriding them in the .env file. */ diff --git a/src/rtk/app/rootReducer.ts b/src/rtk/app/rootReducer.ts index a9d1d2ab3..a2d6092e0 100644 --- a/src/rtk/app/rootReducer.ts +++ b/src/rtk/app/rootReducer.ts @@ -1,6 +1,7 @@ import { combineReducers } from '@reduxjs/toolkit' import myAccount from '../features/accounts/myAccountSlice' import spaceEditors from '../features/accounts/spaceEditorsSlice' +import superLikeCounts from '../features/activeStaking/superLikeCountsSlice' import chainsInfo from '../features/chainsInfo/chainsInfoSlice' import chat from '../features/chat/chatSlice' import enableConfirmation from '../features/confirmationPopup/enableConfirmationSlice' @@ -55,6 +56,7 @@ const rootReducer = combineReducers({ stakes, totalStake, creatorsList, + superLikeCounts, }) export type RootState = ReturnType diff --git a/src/rtk/app/wrappers.ts b/src/rtk/app/wrappers.ts index 6fcb76ec8..b5b2bfad0 100644 --- a/src/rtk/app/wrappers.ts +++ b/src/rtk/app/wrappers.ts @@ -207,12 +207,14 @@ export function createSimpleFetchWrapper({ async (allArgs, { getState, dispatch }): Promise => { const { reload, ...args } = allArgs const id = JSON.stringify(sortKeysRecursive(args)) + + const alreadyFetchedPromise = currentlyFetchingMap.get(id) + if (alreadyFetchedPromise) return alreadyFetchedPromise + if (!reload) { const fetchedData = getCachedData(getState(), allArgs) if (fetchedData && !shouldFetchCondition?.(fetchedData)) return fetchedData } - const alreadyFetchedPromise = currentlyFetchingMap.get(id) - if (alreadyFetchedPromise) return alreadyFetchedPromise const promise = fetchData(allArgs) currentlyFetchingMap.set(id, promise) diff --git a/src/rtk/features/activeStaking/superLikeCountsHooks.ts b/src/rtk/features/activeStaking/superLikeCountsHooks.ts new file mode 100644 index 000000000..731300b0d --- /dev/null +++ b/src/rtk/features/activeStaking/superLikeCountsHooks.ts @@ -0,0 +1,6 @@ +import { useAppSelector } from 'src/rtk/app/store' +import { selectPostSuperLikeCount } from './superLikeCountsSlice' + +export function useSuperLikeCount(postId: string) { + return useAppSelector(state => selectPostSuperLikeCount(state, postId)?.count ?? 0) +} diff --git a/src/rtk/features/activeStaking/superLikeCountsSlice.ts b/src/rtk/features/activeStaking/superLikeCountsSlice.ts new file mode 100644 index 000000000..c3047a9f1 --- /dev/null +++ b/src/rtk/features/activeStaking/superLikeCountsSlice.ts @@ -0,0 +1,60 @@ +import { createEntityAdapter, createSlice } from '@reduxjs/toolkit' +import { getSuperLikeCounts } from 'src/components/utils/datahub/super-likes' +import { RootState } from 'src/rtk/app/rootReducer' +import { createSimpleFetchWrapper } from 'src/rtk/app/wrappers' + +export type SuperLikeCount = { + postId: string + count: number +} + +const sliceName = 'superLikesCounts' + +const adapter = createEntityAdapter({ + selectId: data => data.postId, +}) +const selectors = adapter.getSelectors(state => state.superLikeCounts) + +export const selectPostSuperLikeCount = selectors.selectById +export const selectPostSuperLikeCounts = selectors.selectEntities + +export const fetchSuperLikeCounts = createSimpleFetchWrapper< + { postIds: string[] }, + SuperLikeCount[] +>({ + fetchData: async function ({ postIds }) { + return await getSuperLikeCounts(postIds) + }, + saveToCacheAction: data => slice.actions.setSuperLikeCounts(data), + getCachedData: (state, { postIds }) => { + const entities = selectPostSuperLikeCounts(state) + let isEveryDataCached = true + + const postEntities: SuperLikeCount[] = [] + for (let i = 0; i < postIds.length; i++) { + const postId = postIds[i] + if (!entities[postId]) { + isEveryDataCached = false + break + } else { + postEntities.push(entities[postId]!) + } + } + + if (isEveryDataCached) { + return postEntities + } + return undefined + }, + sliceName, +}) + +const slice = createSlice({ + name: sliceName, + initialState: adapter.getInitialState(), + reducers: { + setSuperLikeCounts: adapter.upsertMany, + }, +}) + +export default slice.reducer diff --git a/src/rtk/features/posts/postsSlice.ts b/src/rtk/features/posts/postsSlice.ts index 0fea53f5c..5137d56b0 100644 --- a/src/rtk/features/posts/postsSlice.ts +++ b/src/rtk/features/posts/postsSlice.ts @@ -40,6 +40,7 @@ import { SpaceData, SpaceStruct, } from 'src/types' +import { fetchSuperLikeCounts } from '../activeStaking/superLikeCountsSlice' import { Content, fetchContents, selectPostContentById } from '../contents/contentsSlice' import { fetchProfileSpaces } from '../profiles/profilesSlice' import { fetchMyReactionsByPostIds } from '../reactions/myPostReactionsSlice' @@ -241,6 +242,7 @@ export const fetchPosts = createAsyncThunk[] = [] + fetches.push(dispatch(fetchSuperLikeCounts({ postIds: entities.map(({ id }) => id) }))) if (withOwner) { const ids = getUniqueOwnerIds(entities) const prefetchedData = generatePrefetchData(