From 4f7af2256b0d3d751cec41556881f0e0e740d5e9 Mon Sep 17 00:00:00 2001 From: mkljczk Date: Sat, 15 Mar 2025 18:38:48 +0100 Subject: [PATCH] pl-fe: migrate domain blocks Signed-off-by: mkljczk --- packages/pl-fe/src/actions/domain-blocks.ts | 109 ------------------ packages/pl-fe/src/components/domain.tsx | 11 +- .../features/account/components/header.tsx | 9 +- .../src/features/domain-blocks/index.tsx | 26 ++--- .../src/queries/settings/domain-blocks.ts | 57 +++++++++ .../pl-fe/src/reducers/domain-lists.test.ts | 12 -- packages/pl-fe/src/reducers/domain-lists.ts | 47 -------- packages/pl-fe/src/reducers/index.ts | 2 - 8 files changed, 79 insertions(+), 194 deletions(-) delete mode 100644 packages/pl-fe/src/actions/domain-blocks.ts create mode 100644 packages/pl-fe/src/queries/settings/domain-blocks.ts delete mode 100644 packages/pl-fe/src/reducers/domain-lists.test.ts delete mode 100644 packages/pl-fe/src/reducers/domain-lists.ts diff --git a/packages/pl-fe/src/actions/domain-blocks.ts b/packages/pl-fe/src/actions/domain-blocks.ts deleted file mode 100644 index 32942685b..000000000 --- a/packages/pl-fe/src/actions/domain-blocks.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Entities } from 'pl-fe/entity-store/entities'; -import { queryClient } from 'pl-fe/queries/client'; -import { isLoggedIn } from 'pl-fe/utils/auth'; - -import { getClient } from '../api'; - -import type { PaginatedResponse } from 'pl-api'; -import type { EntityStore } from 'pl-fe/entity-store/types'; -import type { Account } from 'pl-fe/normalizers/account'; -import type { MinifiedSuggestion } from 'pl-fe/queries/trends/use-suggested-accounts'; -import type { AppDispatch, RootState } from 'pl-fe/store'; - -const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS' as const; - -const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS' as const; - -const DOMAIN_BLOCKS_EXPAND_SUCCESS = 'DOMAIN_BLOCKS_EXPAND_SUCCESS' as const; - -const blockDomain = (domain: string) => - (dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return; - - return getClient(getState).filtering.blockDomain(domain).then(() => { - // TODO: Update relationships on block - const accounts = selectAccountsByDomain(getState(), domain); - if (!accounts) return; - - queryClient.setQueryData>(['suggestions'], suggestions => suggestions - ? suggestions.filter((suggestion) => !accounts.includes(suggestion.account_id)) - : undefined); - }); - }; - -const unblockDomain = (domain: string) => - (dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return; - - return getClient(getState).filtering.unblockDomain(domain).then(() => { - // TODO: Update relationships on unblock - const accounts = selectAccountsByDomain(getState(), domain); - if (!accounts) return; - dispatch(unblockDomainSuccess(domain, accounts)); - }).catch(() => {}); - }; - -const unblockDomainSuccess = (domain: string, accounts: string[]) => ({ - type: DOMAIN_UNBLOCK_SUCCESS, - domain, - accounts, -}); - -const fetchDomainBlocks = () => - (dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return; - - return getClient(getState).filtering.getDomainBlocks().then(response => { - dispatch(fetchDomainBlocksSuccess(response.items, response.next)); - }); - }; - -const fetchDomainBlocksSuccess = (domains: string[], next: (() => Promise>) | null) => ({ - type: DOMAIN_BLOCKS_FETCH_SUCCESS, - domains, - next, -}); - -const expandDomainBlocks = () => - (dispatch: AppDispatch, getState: () => RootState) => { - if (!isLoggedIn(getState)) return; - - const next = getState().domain_lists.blocks.next; - - if (!next) return; - - next().then(response => { - dispatch(expandDomainBlocksSuccess(response.items, response.next)); - }).catch(() => {}); - }; - -const selectAccountsByDomain = (state: RootState, domain: string): string[] => { - const store = state.entities[Entities.ACCOUNTS]?.store as EntityStore | undefined; - const entries = store ? Object.entries(store) : undefined; - const accounts = entries - ?.filter(([_, item]) => item && item.acct.endsWith(`@${domain}`)) - .map(([_, item]) => item!.id); - return accounts || []; -}; - -const expandDomainBlocksSuccess = (domains: string[], next: (() => Promise>) | null) => ({ - type: DOMAIN_BLOCKS_EXPAND_SUCCESS, - domains, - next, -}); - -type DomainBlocksAction = - | ReturnType - | ReturnType - | ReturnType; - -export { - DOMAIN_UNBLOCK_SUCCESS, - DOMAIN_BLOCKS_FETCH_SUCCESS, - DOMAIN_BLOCKS_EXPAND_SUCCESS, - blockDomain, - unblockDomain, - fetchDomainBlocks, - expandDomainBlocks, - type DomainBlocksAction, -}; diff --git a/packages/pl-fe/src/components/domain.tsx b/packages/pl-fe/src/components/domain.tsx index ec7c899cb..13dc3ab5a 100644 --- a/packages/pl-fe/src/components/domain.tsx +++ b/packages/pl-fe/src/components/domain.tsx @@ -1,11 +1,11 @@ +import { useMutation } from '@tanstack/react-query'; import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { unblockDomain } from 'pl-fe/actions/domain-blocks'; import HStack from 'pl-fe/components/ui/hstack'; import IconButton from 'pl-fe/components/ui/icon-button'; import Text from 'pl-fe/components/ui/text'; -import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; +import { unblockDomainMutationOptions } from 'pl-fe/queries/settings/domain-blocks'; const messages = defineMessages({ blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' }, @@ -17,20 +17,21 @@ interface IDomain { } const Domain: React.FC = ({ domain }) => { - const dispatch = useAppDispatch(); const intl = useIntl(); + const { mutate: unblockDomain } = useMutation(unblockDomainMutationOptions); + // const onBlockDomain = () => { // openModal('CONFIRM', { // heading: , // message: {domain} }} />, // confirm: intl.formatMessage(messages.blockDomainConfirm), - // onConfirm: () => dispatch(blockDomain(domain)), + // onConfirm: () => blockDomain(domain), // }); // } const handleDomainUnblock = () => { - dispatch(unblockDomain(domain)); + unblockDomain(domain); }; return ( diff --git a/packages/pl-fe/src/features/account/components/header.tsx b/packages/pl-fe/src/features/account/components/header.tsx index 4847b75fd..3b74b4770 100644 --- a/packages/pl-fe/src/features/account/components/header.tsx +++ b/packages/pl-fe/src/features/account/components/header.tsx @@ -7,7 +7,6 @@ import * as v from 'valibot'; import { biteAccount, blockAccount, pinAccount, removeFromFollowers, unblockAccount, unmuteAccount, unpinAccount } from 'pl-fe/actions/accounts'; import { mentionCompose, directCompose } from 'pl-fe/actions/compose'; -import { blockDomain, unblockDomain } from 'pl-fe/actions/domain-blocks'; import { initReport, ReportableEntities } from 'pl-fe/actions/reports'; import { useFollow } from 'pl-fe/api/hooks/accounts/use-follow'; import Badge from 'pl-fe/components/badge'; @@ -26,6 +25,7 @@ import { useFeatures } from 'pl-fe/hooks/use-features'; import { useOwnAccount } from 'pl-fe/hooks/use-own-account'; import { useChats } from 'pl-fe/queries/chats'; import { queryClient } from 'pl-fe/queries/client'; +import { blockDomainMutationOptions, unblockDomainMutationOptions } from 'pl-fe/queries/settings/domain-blocks'; import { useModalsStore } from 'pl-fe/stores/modals'; import { useSettingsStore } from 'pl-fe/stores/settings'; import toast from 'pl-fe/toast'; @@ -100,6 +100,9 @@ const Header: React.FC = ({ account }) => { const { getOrCreateChatByAccountId } = useChats(); + const { mutate: blockDomain } = useMutation(blockDomainMutationOptions); + const { mutate: unblockDomain } = useMutation(unblockDomainMutationOptions); + const createAndNavigateToChat = useMutation({ mutationFn: (accountId: string) => getOrCreateChatByAccountId(accountId), onError: (error: { response: PlfeResponse }) => { @@ -203,12 +206,12 @@ const Header: React.FC = ({ account }) => { heading: , message: {domain} }} />, confirm: intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => dispatch(blockDomain(domain)), + onConfirm: () => blockDomain(domain), }); }; const onUnblockDomain = (domain: string) => { - dispatch(unblockDomain(domain)); + unblockDomain(domain); }; const onProfileExternal = (url: string) => { diff --git a/packages/pl-fe/src/features/domain-blocks/index.tsx b/packages/pl-fe/src/features/domain-blocks/index.tsx index f60799a4e..b2d3ff4b6 100644 --- a/packages/pl-fe/src/features/domain-blocks/index.tsx +++ b/packages/pl-fe/src/features/domain-blocks/index.tsx @@ -1,34 +1,28 @@ -import debounce from 'lodash/debounce'; +import { useInfiniteQuery } from '@tanstack/react-query'; import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import { fetchDomainBlocks, expandDomainBlocks } from 'pl-fe/actions/domain-blocks'; import Domain from 'pl-fe/components/domain'; import ScrollableList from 'pl-fe/components/scrollable-list'; import Column from 'pl-fe/components/ui/column'; import Spinner from 'pl-fe/components/ui/spinner'; -import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; -import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; +import { domainBlocksQueryOptions } from 'pl-fe/queries/settings/domain-blocks'; const messages = defineMessages({ heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' }, unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, }); -const handleLoadMore = debounce((dispatch) => { - dispatch(expandDomainBlocks()); -}, 300, { leading: true }); - const DomainBlocks: React.FC = () => { - const dispatch = useAppDispatch(); const intl = useIntl(); - const domains = useAppSelector((state) => state.domain_lists.blocks.items); - const hasMore = useAppSelector((state) => !!state.domain_lists.blocks.next); + const { data: domains, hasNextPage, fetchNextPage } = useInfiniteQuery(domainBlocksQueryOptions); - React.useEffect(() => { - dispatch(fetchDomainBlocks()); - }, []); + const handleLoadMore = () => { + if (hasNextPage) { + fetchNextPage({ cancelRefetch: false }); + } + }; if (!domains) { return ( @@ -44,8 +38,8 @@ const DomainBlocks: React.FC = () => { handleLoadMore(dispatch)} - hasMore={hasMore} + onLoadMore={handleLoadMore} + hasMore={hasNextPage} emptyMessage={emptyMessage} listClassName='divide-y divide-gray-200 dark:divide-gray-800' > diff --git a/packages/pl-fe/src/queries/settings/domain-blocks.ts b/packages/pl-fe/src/queries/settings/domain-blocks.ts new file mode 100644 index 000000000..da6bf5d71 --- /dev/null +++ b/packages/pl-fe/src/queries/settings/domain-blocks.ts @@ -0,0 +1,57 @@ +import { getClient } from 'pl-fe/api'; +import { Entities } from 'pl-fe/entity-store/entities'; + +import { queryClient } from '../client'; +import { makePaginatedResponseQueryOptions } from '../utils/make-paginated-response-query-options'; +import { mutationOptions } from '../utils/mutation-options'; + +import type { MinifiedSuggestion } from '../trends/use-suggested-accounts'; +import type { EntityStore } from 'pl-fe/entity-store/types'; +import type { Account } from 'pl-fe/normalizers/account'; +import type { RootState, Store } from 'pl-fe/store'; + +let store: Store; +import('pl-fe/store').then((value) => store = value.store).catch(() => {}); + +const domainBlocksQueryOptions = makePaginatedResponseQueryOptions( + ['settings', 'domainBlocks'], + (client) => client.filtering.getDomainBlocks(), +)(); + +const blockDomainMutationOptions = mutationOptions({ + mutationKey: ['settings', 'domainBlocks'], + mutationFn: (domain: string) => getClient().filtering.blockDomain(domain), + onSettled: (_, __, domain) => { + queryClient.invalidateQueries(domainBlocksQueryOptions); + + const accounts = selectAccountsByDomain(store.getState(), domain); + if (!accounts) return; + + queryClient.setQueryData>(['suggestions'], suggestions => suggestions + ? suggestions.filter((suggestion) => !accounts.includes(suggestion.account_id)) + : undefined); + }, +}); + +const unblockDomainMutationOptions = mutationOptions({ + mutationKey: ['settings', 'domainBlocks'], + mutationFn: (domain: string) => getClient().filtering.unblockDomain(domain), + onSettled: () => { + queryClient.invalidateQueries(domainBlocksQueryOptions); + }, +}); + +const selectAccountsByDomain = (state: RootState, domain: string): string[] => { + const store = state.entities[Entities.ACCOUNTS]?.store as EntityStore | undefined; + const entries = store ? Object.entries(store) : undefined; + const accounts = entries + ?.filter(([_, item]) => item && item.acct.endsWith(`@${domain}`)) + .map(([_, item]) => item!.id); + return accounts || []; +}; + +export { + domainBlocksQueryOptions, + blockDomainMutationOptions, + unblockDomainMutationOptions, +}; diff --git a/packages/pl-fe/src/reducers/domain-lists.test.ts b/packages/pl-fe/src/reducers/domain-lists.test.ts deleted file mode 100644 index e990d2d32..000000000 --- a/packages/pl-fe/src/reducers/domain-lists.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import reducer from './domain-lists'; - -describe('domain_lists reducer', () => { - it('should return the initial state', () => { - expect(reducer(undefined, {} as any).toJS()).toEqual({ - blocks: { - items: [], - next: null, - }, - }); - }); -}); diff --git a/packages/pl-fe/src/reducers/domain-lists.ts b/packages/pl-fe/src/reducers/domain-lists.ts deleted file mode 100644 index 9aea3238e..000000000 --- a/packages/pl-fe/src/reducers/domain-lists.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { create } from 'mutative'; - -import { - DOMAIN_BLOCKS_FETCH_SUCCESS, - DOMAIN_BLOCKS_EXPAND_SUCCESS, - DOMAIN_UNBLOCK_SUCCESS, - type DomainBlocksAction, -} from '../actions/domain-blocks'; - -import type { PaginatedResponse } from 'pl-api'; - -interface State { - blocks: { - items: Array; - next: (() => Promise>) | null; - }; -} - -const initialState: State = { - blocks: { - items: [], - next: null, - }, -}; - -const domainLists = (state: State = initialState, action: DomainBlocksAction): State => { - switch (action.type) { - case DOMAIN_BLOCKS_FETCH_SUCCESS: - return create(state, (draft) => { - draft.blocks.items = action.domains; - draft.blocks.next = action.next; - }); - case DOMAIN_BLOCKS_EXPAND_SUCCESS: - return create(state, (draft) => { - draft.blocks.items = [...new Set([...draft.blocks.items, ...action.domains])]; - draft.blocks.next = action.next; - }); - case DOMAIN_UNBLOCK_SUCCESS: - return create(state, (draft) => { - draft.blocks.items = draft.blocks.items.filter(item => item !== action.domain); - }); - default: - return state; - } -}; - -export { domainLists as default }; diff --git a/packages/pl-fe/src/reducers/index.ts b/packages/pl-fe/src/reducers/index.ts index da86c668b..fbe3e846d 100644 --- a/packages/pl-fe/src/reducers/index.ts +++ b/packages/pl-fe/src/reducers/index.ts @@ -12,7 +12,6 @@ import auth from './auth'; import compose from './compose'; import contexts from './contexts'; import conversations from './conversations'; -import domain_lists from './domain-lists'; import draft_statuses from './draft-statuses'; import filters from './filters'; import instance from './instance'; @@ -41,7 +40,6 @@ const reducers = { compose, contexts, conversations, - domain_lists, draft_statuses, entities, filters,