pl-fe: account relationships migration

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2025-10-23 15:41:01 +02:00
parent 593b35b21f
commit a922287246
20 changed files with 148 additions and 198 deletions

View File

@@ -5,7 +5,6 @@ import {
type Relationship,
} from 'pl-api';
import { Entities } from 'pl-fe/entity-store/entities';
import { queryClient } from 'pl-fe/queries/client';
import { selectAccount } from 'pl-fe/selectors';
import { isLoggedIn } from 'pl-fe/utils/auth';
@@ -177,8 +176,7 @@ const fetchRelationships = (accountIds: string[]) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
const loadedRelationships = getState().entities[Entities.RELATIONSHIPS]?.store;
const newAccountIds = accountIds.filter(id => !loadedRelationships?.[id]);
const newAccountIds = accountIds.filter(id => !queryClient.getQueryData(['accountRelationships', id]));
if (newAccountIds.length === 0) {
return null;

View File

@@ -120,7 +120,11 @@ const importEntities = (entities: {
queryClient.setQueryData<BasePoll>(['statuses', 'polls', poll.id], poll);
}
}
if (!isEmpty(relationships)) dispatch(importEntityStoreEntities(Object.values(relationships), Entities.RELATIONSHIPS));
if (!isEmpty(relationships)) {
for (const relationship of Object.values(relationships)) {
queryClient.setQueryData<BaseRelationship>(['accountRelationships', relationship.id], relationship);
}
}
if (!isEmpty(statuses)) dispatch<ImportStatusesAction>({ type: STATUSES_IMPORT, statuses: Object.values(statuses) });
};

View File

@@ -6,8 +6,7 @@ import { useClient } from 'pl-fe/hooks/use-client';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
import { type Account, normalizeAccount } from 'pl-fe/normalizers/account';
import { useRelationship } from './use-relationship';
import { useRelationshipQuery } from 'pl-fe/queries/accounts/use-relationship';
import type { Account as BaseAccount } from 'pl-api';
@@ -29,9 +28,9 @@ const useAccountLookup = (acct: string | undefined, opts: UseAccountLookupOpts =
);
const {
relationship,
data: relationship,
isLoading: isRelationshipLoading,
} = useRelationship(entity?.id, { enabled: withRelationship });
} = useRelationshipQuery(withRelationship ? entity?.id : undefined);
const isBlocked = entity?.relationship?.blocked_by === true;
const isUnavailable = (me === entity?.id) ? false : (isBlocked && !features.blockersVisible);

View File

@@ -7,8 +7,7 @@ import { useClient } from 'pl-fe/hooks/use-client';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
import { type Account, normalizeAccount } from 'pl-fe/normalizers/account';
import { useRelationship } from './use-relationship';
import { useRelationshipQuery } from 'pl-fe/queries/accounts/use-relationship';
import type { Account as BaseAccount } from 'pl-api';
@@ -31,9 +30,9 @@ const useAccount = (accountId?: string, opts: UseAccountOpts = {}) => {
const meta = useAppSelector((state) => accountId ? state.accounts_meta[accountId] : undefined);
const {
relationship,
data: relationship,
isLoading: isRelationshipLoading,
} = useRelationship(accountId, { enabled: withRelationship });
} = useRelationshipQuery(withRelationship ? entity?.id : undefined);
const isBlocked = entity?.relationship?.blocked_by === true;
const isUnavailable = (me === entity?.id) ? false : (isBlocked && !features.blockersVisible);

View File

@@ -1,88 +0,0 @@
import { importEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import { useTransaction } from 'pl-fe/entity-store/hooks/use-transaction';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useClient } from 'pl-fe/hooks/use-client';
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
interface FollowOpts {
reblogs?: boolean;
notify?: boolean;
languages?: string[];
}
const useFollow = () => {
const client = useClient();
const dispatch = useAppDispatch();
const { isLoggedIn } = useLoggedIn();
const { transaction } = useTransaction();
const followEffect = (accountId: string) => {
transaction({
Accounts: {
[accountId]: (account) => ({
...account,
followers_count: account.followers_count + 1,
}),
},
Relationships: {
[accountId]: (relationship) => ({
...relationship,
following: true,
}),
},
});
};
const unfollowEffect = (accountId: string) => {
transaction({
Accounts: {
[accountId]: (account) => ({
...account,
followers_count: Math.max(0, account.followers_count - 1),
}),
},
Relationships: {
[accountId]: (relationship) => ({
...relationship,
following: false,
requested: false,
}),
},
});
};
const follow = async (accountId: string, options: FollowOpts = {}) => {
if (!isLoggedIn) return;
followEffect(accountId);
try {
const response = await client.accounts.followAccount(accountId, options);
if (response.id) {
dispatch(importEntities([response], Entities.RELATIONSHIPS));
}
} catch (e) {
unfollowEffect(accountId);
}
};
const unfollow = async (accountId: string) => {
if (!isLoggedIn) return;
unfollowEffect(accountId);
try {
await client.accounts.unfollowAccount(accountId);
} catch (e) {
followEffect(accountId);
}
};
return {
follow,
unfollow,
followEffect,
unfollowEffect,
};
};
export { useFollow };

View File

@@ -1,31 +0,0 @@
import * as v from 'valibot';
import { Entities } from 'pl-fe/entity-store/entities';
import { useEntity } from 'pl-fe/entity-store/hooks/use-entity';
import { useClient } from 'pl-fe/hooks/use-client';
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
import type { Relationship } from 'pl-api';
interface UseRelationshipOpts {
enabled?: boolean;
}
const useRelationship = (accountId: string | undefined, opts: UseRelationshipOpts = {}) => {
const client = useClient();
const { isLoggedIn } = useLoggedIn();
const { enabled = false } = opts;
const { entity: relationship, ...result } = useEntity<Relationship>(
[Entities.RELATIONSHIPS, accountId!],
() => client.accounts.getRelationships([accountId!]),
{
enabled: enabled && isLoggedIn && !!accountId,
schema: v.pipe(v.any(), v.transform(arr => arr[0])),
},
);
return { relationship, ...result };
};
export { useRelationship };

View File

@@ -8,9 +8,6 @@ import { getLocale } from 'pl-fe/actions/settings';
import { updateStatus } from 'pl-fe/actions/statuses';
import { deleteFromTimelines, processTimelineUpdate } from 'pl-fe/actions/timelines';
import { useStatContext } from 'pl-fe/contexts/stat-context';
import { importEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import { selectEntity } from 'pl-fe/entity-store/selectors';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
import messages from 'pl-fe/messages';
@@ -73,16 +70,12 @@ const updateFollowRelationships = (update: FollowRelationshipUpdate) =>
const state = getState();
const me = state.me;
const relationship = selectEntity<Relationship>(state, Entities.RELATIONSHIPS, update.following.id);
if (update.follower.id === me && relationship) {
const updated = {
if (update.follower.id === me) {
queryClient.setQueryData<Relationship>(['accountRelationships', update.following.id], (relationship) => relationship ? ({
...relationship,
...followStateToRelationship(update.state),
};
// Add a small delay to deal with API race conditions.
setTimeout(() => dispatch(importEntities([updated], Entities.RELATIONSHIPS)), 300);
}) : undefined);
}
};

View File

@@ -1,4 +1,4 @@
import type { GroupMember, GroupRelationship, Relationship } from 'pl-api';
import type { GroupMember, GroupRelationship } from 'pl-api';
import type { Account } from 'pl-fe/normalizers/account';
import type { Group } from 'pl-fe/normalizers/group';
@@ -7,7 +7,6 @@ enum Entities {
GROUPS = 'Groups',
GROUP_MEMBERSHIPS = 'GroupMemberships',
GROUP_RELATIONSHIPS = 'GroupRelationships',
RELATIONSHIPS = 'Relationships',
}
interface EntityTypes {
@@ -15,7 +14,6 @@ interface EntityTypes {
[Entities.GROUPS]: Group;
[Entities.GROUP_MEMBERSHIPS]: GroupMember;
[Entities.GROUP_RELATIONSHIPS]: GroupRelationship;
[Entities.RELATIONSHIPS]: Relationship;
}
export { Entities, type EntityTypes };

View File

@@ -9,7 +9,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 { initReport, ReportableEntities } from 'pl-fe/actions/reports';
import { useFollow } from 'pl-fe/api/hooks/accounts/use-follow';
import Account from 'pl-fe/components/account';
import AltIndicator from 'pl-fe/components/alt-indicator';
import Badge from 'pl-fe/components/badge';
@@ -30,6 +29,7 @@ import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useClient } from 'pl-fe/hooks/use-client';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useOwnAccount } from 'pl-fe/hooks/use-own-account';
import { useFollowMutation } from 'pl-fe/queries/accounts/use-relationship';
import { useChats } from 'pl-fe/queries/chats';
import { queryClient } from 'pl-fe/queries/client';
import { blockDomainMutationOptions, unblockDomainMutationOptions } from 'pl-fe/queries/settings/domain-blocks';
@@ -134,7 +134,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
const features = useFeatures();
const { account: ownAccount } = useOwnAccount();
const { follow } = useFollow();
const { mutate: follow } = useFollowMutation(account?.id!);
const { openModal } = useModalsActions();
const settings = useSettings();
@@ -207,9 +207,9 @@ const Header: React.FC<IHeader> = ({ account }) => {
const onReblogToggle = () => {
if (account.relationship?.showing_reblogs) {
follow(account.id, { reblogs: false });
follow({ reblogs: false });
} else {
follow(account.id, { reblogs: true });
follow({ reblogs: true });
}
};

View File

@@ -2,7 +2,6 @@ import React, { useState } from 'react';
import { defineMessages, IntlShape, useIntl } from 'react-intl';
import { unblockAccount } from 'pl-fe/actions/accounts';
import { useRelationship } from 'pl-fe/api/hooks/accounts/use-relationship';
import Button from 'pl-fe/components/ui/button';
import Combobox, { ComboboxInput, ComboboxList, ComboboxOption, ComboboxPopover } from 'pl-fe/components/ui/combobox';
import HStack from 'pl-fe/components/ui/hstack';
@@ -14,6 +13,7 @@ import UploadButton from 'pl-fe/features/compose/components/upload-button';
import emojiSearch from 'pl-fe/features/emoji/search';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useInstance } from 'pl-fe/hooks/use-instance';
import { useRelationshipQuery } from 'pl-fe/queries/accounts/use-relationship';
import { useModalsActions } from 'pl-fe/stores/modals';
import { textAtCursorMatchesToken } from 'pl-fe/utils/suggestions';
@@ -80,7 +80,7 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
const { openModal } = useModalsActions();
const { chat } = useChatContext();
const { relationship } = useRelationship(chat?.account.id, { enabled: !!chat });
const { data: relationship } = useRelationshipQuery(chat?.account.id);
const isBlocked = relationship?.blocked_by && false;
const isBlocking = relationship?.blocking && false;

View File

@@ -2,7 +2,6 @@ import React, { useMemo } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { useRelationship } from 'pl-fe/api/hooks/accounts/use-relationship';
import DropdownMenu from 'pl-fe/components/dropdown-menu';
import { ParsedContent } from 'pl-fe/components/parsed-content';
import RelativeTimestamp from 'pl-fe/components/relative-timestamp';
@@ -14,6 +13,7 @@ import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
import { useChatContext } from 'pl-fe/contexts/chat-context';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useRelationshipQuery } from 'pl-fe/queries/accounts/use-relationship';
import { useChatActions } from 'pl-fe/queries/chats';
import { useModalsActions } from 'pl-fe/stores/modals';
@@ -41,7 +41,7 @@ const ChatListItem: React.FC<IChatListItemInterface> = ({ chat, onClick }) => {
const { isUsingMainChatPage } = useChatContext();
const { deleteChat } = useChatActions(chat?.id as string);
const { relationship } = useRelationship(chat?.account.id, { enabled: !!chat });
const { data: relationship } = useRelationshipQuery(chat?.account.id);
const isBlocked = relationship?.blocked_by && false;
const isBlocking = relationship?.blocking && false;

View File

@@ -8,14 +8,13 @@ import Divider from 'pl-fe/components/ui/divider';
import Spinner from 'pl-fe/components/ui/spinner';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import { Entities } from 'pl-fe/entity-store/entities';
import PlaceholderChatMessage from 'pl-fe/features/placeholder/components/placeholder-chat-message';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useRelationshipQuery } from 'pl-fe/queries/accounts/use-relationship';
import { useChatActions, useChatMessages } from 'pl-fe/queries/chats';
import ChatMessage from './chat-message';
import type { Chat, Relationship } from 'pl-api';
import type { Chat } from 'pl-api';
import type { ChatMessage as ChatMessageEntity } from 'pl-fe/normalizers/chat-message';
const messages = defineMessages({
@@ -86,9 +85,9 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
refetch,
} = useChatMessages(chat);
const formattedChatMessages = chatMessages || [];
const isBlocked = !!useRelationshipQuery(chat.account.id).data?.blocked_by;
const isBlocked = !!useAppSelector((state) => (state.entities[Entities.RELATIONSHIPS]?.store[chat.account.id] as Relationship)?.blocked_by);
const formattedChatMessages = chatMessages || [];
const lastChatMessage = chatMessages ? chatMessages[chatMessages.length - 1] : null;

View File

@@ -11,10 +11,9 @@ import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
import { useChatContext } from 'pl-fe/contexts/chat-context';
import { Entities } from 'pl-fe/entity-store/entities';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useRelationshipQuery } from 'pl-fe/queries/accounts/use-relationship';
import { useChat, useChatActions, useChats } from 'pl-fe/queries/chats';
import { useModalsActions } from 'pl-fe/stores/modals';
@@ -23,8 +22,6 @@ import Chat from '../../chat';
import BlankslateEmpty from './blankslate-empty';
import BlankslateWithChats from './blankslate-with-chats';
import type { Relationship } from 'pl-api';
const messages = defineMessages({
blockMessage: { id: 'chat_settings.block.message', defaultMessage: 'Blocking will prevent this profile from direct messaging you and viewing your content. You can unblock later.' },
blockHeading: { id: 'chat_settings.block.heading', defaultMessage: 'Block @{acct}' },
@@ -57,7 +54,7 @@ const ChatPageMain = () => {
const { deleteChat } = useChatActions(chat?.id as string);
const isBlocking = !!useAppSelector((state) => chat?.account?.id && (state.entities[Entities.RELATIONSHIPS]?.store[chat.account.id] as Relationship)?.blocked_by);
const isBlocked = !!useRelationshipQuery(chat?.account.id).data?.blocked_by;
const handleBlockUser = () => {
openModal('CONFIRM', {
@@ -114,8 +111,8 @@ const ChatPageMain = () => {
const menuItems: Menu = [
{
icon: require('@phosphor-icons/core/regular/prohibit.svg'),
text: intl.formatMessage(isBlocking ? messages.unblockUser : messages.blockUser, { acct: chat.account.acct }),
action: isBlocking ? handleUnblockUser : handleBlockUser,
text: intl.formatMessage(isBlocked ? messages.unblockUser : messages.blockUser, { acct: chat.account.acct }),
action: isBlocked ? handleUnblockUser : handleBlockUser,
},
];

View File

@@ -8,17 +8,14 @@ import Icon from 'pl-fe/components/ui/icon';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import { ChatWidgetScreens, useChatContext } from 'pl-fe/contexts/chat-context';
import { Entities } from 'pl-fe/entity-store/entities';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useRelationshipQuery } from 'pl-fe/queries/accounts/use-relationship';
import { useChatActions } from 'pl-fe/queries/chats';
import { useModalsActions } from 'pl-fe/stores/modals';
import ChatPaneHeader from './chat-pane-header';
import type { Relationship } from 'pl-api';
const messages = defineMessages({
blockMessage: { id: 'chat_settings.block.message', defaultMessage: 'Blocking will prevent this profile from direct messaging you and viewing your content. You can unblock later.' },
blockHeading: { id: 'chat_settings.block.heading', defaultMessage: 'Block @{acct}' },
@@ -44,7 +41,7 @@ const ChatSettings = () => {
const { chat, changeScreen, toggleChatPane } = useChatContext();
const { deleteChat } = useChatActions(chat?.id as string);
const isBlocking = !!useAppSelector((state) => chat?.account?.id && (state.entities[Entities.RELATIONSHIPS]?.store[chat.account.id] as Relationship)?.blocked_by);
const isBlocked = !!useRelationshipQuery(chat?.account.id).data?.blocked_by;
const closeSettings = () => {
changeScreen(ChatWidgetScreens.CHAT, chat?.id);
@@ -121,9 +118,9 @@ const ChatSettings = () => {
</HStack>
<Stack space={5}>
<button onClick={isBlocking ? handleUnblockUser : handleBlockUser} className='flex w-full items-center space-x-2 text-sm font-bold text-primary-600 dark:text-accent-blue'>
<button onClick={isBlocked ? handleUnblockUser : handleBlockUser} className='flex w-full items-center space-x-2 text-sm font-bold text-primary-600 dark:text-accent-blue'>
<Icon src={require('@phosphor-icons/core/regular/prohibit.svg')} className='size-5' />
<span>{intl.formatMessage(isBlocking ? messages.unblockUser : messages.blockUser, { acct: chat.account.acct })}</span>
<span>{intl.formatMessage(isBlocked ? messages.unblockUser : messages.blockUser, { acct: chat.account.acct })}</span>
</button>
{features.chatsDelete && (

View File

@@ -5,7 +5,7 @@ import { changeSetting, saveSettings } from 'pl-fe/actions/settings';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useTheme } from 'pl-fe/hooks/use-theme';
import { useCustomEmojis } from 'pl-fe/queries/instance/use-custom-emojis';
import { useSettings , useSettingsStoreActions } from 'pl-fe/stores/settings';
import { useSettings, useSettingsStoreActions } from 'pl-fe/stores/settings';
import { buildCustomEmojiCategories } from '../../emoji';
import { EmojiPicker } from '../../ui/util/async-components';

View File

@@ -8,8 +8,6 @@ import {
unmuteAccount,
biteAccount,
} from 'pl-fe/actions/accounts';
import { useFollow } from 'pl-fe/api/hooks/accounts/use-follow';
import { useRelationship } from 'pl-fe/api/hooks/accounts/use-relationship';
import Button from 'pl-fe/components/ui/button';
import HStack from 'pl-fe/components/ui/hstack';
import Spinner from 'pl-fe/components/ui/spinner';
@@ -17,6 +15,7 @@ import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
import { useAcceptFollowRequestMutation, useRejectFollowRequestMutation } from 'pl-fe/queries/accounts/use-follow-requests';
import { useRelationshipQuery, useFollowMutation, useUnfollowMutation } from 'pl-fe/queries/accounts/use-relationship';
import { useModalsActions } from 'pl-fe/stores/modals';
import toast from 'pl-fe/toast';
@@ -62,18 +61,20 @@ const ActionButton: React.FC<IActionButton> = ({ account, actionType, small = tr
const { openModal } = useModalsActions();
const { isLoggedIn, me } = useLoggedIn();
const { follow, unfollow } = useFollow();
const { relationship, isLoading } = useRelationship(account.id, { enabled: true });
const { mutate: follow, isPending: isPendingFollow } = useFollowMutation(account.id);
const { mutate: unfollow, isPending: isPendingUnfollow } = useUnfollowMutation(account.id);
const { data: relationship, isLoading } = useRelationshipQuery(account.id);
const { mutate: authorizeFollowRequest } = useAcceptFollowRequestMutation(account.id);
const { mutate: rejectFollowRequest } = useRejectFollowRequestMutation(account.id);
const handleFollow = () => {
if (relationship?.following || relationship?.requested) {
unfollow(account.id);
unfollow();
} else {
follow(account.id);
follow(undefined);
}
};
@@ -240,9 +241,9 @@ const ActionButton: React.FC<IActionButton> = ({ account, actionType, small = tr
}
}
if (!relationship && !isLoading) return null;
if (!relationship) {
if (!isLoading) return null;
// Wait until the relationship is loaded
return (
<Button
size='xs'
@@ -267,7 +268,7 @@ const ActionButton: React.FC<IActionButton> = ({ account, actionType, small = tr
return (
<Button
size='sm'
disabled={blockedBy}
disabled={blockedBy || isPendingFollow || isPendingUnfollow}
theme={isFollowing ? 'secondary' : 'primary'}
icon={blockedBy ? require('@phosphor-icons/core/regular/prohibit.svg') : (!isFollowing && require('@phosphor-icons/core/regular/plus.svg'))}
onClick={handleFollow}

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useFollow } from 'pl-fe/api/hooks/accounts/use-follow';
import IconButton from 'pl-fe/components/ui/icon-button';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useFollowMutation } from 'pl-fe/queries/accounts/use-relationship';
import toast from 'pl-fe/toast';
import type { Account as AccountEntity } from 'pl-fe/normalizers/account';
@@ -24,7 +24,7 @@ interface ISubscriptionButton {
const SubscriptionButton = ({ account }: ISubscriptionButton) => {
const features = useFeatures();
const intl = useIntl();
const { follow } = useFollow();
const { mutate: follow, isPending } = useFollowMutation(account.id);
const isFollowing = account.relationship?.following;
const isRequested = account.relationship?.requested;
@@ -35,13 +35,15 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => {
const onNotifyToggle = () => {
if (account.relationship?.notifying) {
follow(account.id, { notify: false })
?.then(() => toast.success(intl.formatMessage(messages.unsubscribeSuccess)))
.catch(() => toast.error(intl.formatMessage(messages.unsubscribeFailure)));
follow({ notify: false }, {
onSuccess: () => toast.success(intl.formatMessage(messages.unsubscribeSuccess)),
onError: () => toast.error(intl.formatMessage(messages.unsubscribeFailure)),
});
} else {
follow(account.id, { notify: true })
?.then(() => toast.success(intl.formatMessage(messages.subscribeSuccess)))
.catch(() => toast.error(intl.formatMessage(messages.subscribeFailure)));
follow({ notify: true }, {
onSuccess: () => toast.success(intl.formatMessage(messages.subscribeSuccess)),
onError: () => toast.error(intl.formatMessage(messages.subscribeFailure)),
});
}
};
@@ -58,6 +60,7 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => {
<IconButton
src={isSubscribed ? require('@phosphor-icons/core/regular/bell-simple-ringing.svg') : require('@phosphor-icons/core/regular/bell-simple.svg')}
onClick={handleToggle}
disabled={isPending}
title={title}
theme='outlined'
className='px-2'

View File

@@ -1,12 +1,7 @@
import * as plapi from 'pl-api';
import { type Features } from 'pl-api';
import * as v from 'valibot';
import { useAppSelector } from './use-app-selector';
(window as any).v = v;
(window as any).plapi = plapi;
/** Get features for the current instance. */
const useFeatures = (): Features => ({ ...useAppSelector(state => state.auth.client.features), filtersV2BlurAction: true });

View File

@@ -0,0 +1,87 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useClient } from 'pl-fe/hooks/use-client';
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
import type { FollowAccountParams, Relationship } from 'pl-api';
const useRelationshipQuery = (accountId?: string) => {
const client = useClient();
const { isLoggedIn } = useLoggedIn();
return useQuery({
queryKey: ['accountRelationships', accountId],
queryFn: () => client.accounts.getRelationships([accountId!]).then(arr => arr[0]),
enabled: isLoggedIn && !!accountId,
});
};
const useFollowMutation = (accountId: string) => {
const client = useClient();
const queryClient = useQueryClient();
return useMutation({
mutationKey: ['accountRelationships', accountId],
mutationFn: (params?: FollowAccountParams) => client.accounts.followAccount(accountId, params),
onMutate: (params) => {
const previousRelationship = queryClient.getQueryData<Relationship>(['accountRelationships', accountId])!;
if (!previousRelationship) return;
const newRelationship: Relationship = {
...previousRelationship,
requested: !previousRelationship.following,
notifying: params?.notify ?? previousRelationship.notifying,
showing_reblogs: params?.reblogs ?? previousRelationship.showing_reblogs,
};
queryClient.setQueryData(['accountRelationships', accountId], newRelationship);
return { previousRelationship };
},
onError: (_err, _variables, context) => {
if (context?.previousRelationship) {
queryClient.setQueryData(['accountRelationships', accountId], context.previousRelationship);
}
},
onSuccess: (data) => {
queryClient.setQueryData(['accountRelationships', accountId], data);
},
});
};
const useUnfollowMutation = (accountId: string) => {
const client = useClient();
const queryClient = useQueryClient();
return useMutation({
mutationKey: ['accountRelationships', accountId],
mutationFn: () => client.accounts.unfollowAccount(accountId),
onMutate: () => {
const previousRelationship = queryClient.getQueryData<Relationship>(['accountRelationships', accountId])!;
if (!previousRelationship) return;
const newRelationship: Relationship = {
...previousRelationship,
following: false,
requested: false,
notifying: false,
showing_reblogs: false,
};
queryClient.setQueryData(['accountRelationships', accountId], newRelationship);
return { previousRelationship };
},
onError: (_err, _variables, context) => {
if (context?.previousRelationship) {
queryClient.setQueryData(['accountRelationships', accountId], context.previousRelationship);
}
},
onSuccess: (data) => {
queryClient.setQueryData(['accountRelationships', accountId], data);
},
});
};
export { useRelationshipQuery, useFollowMutation, useUnfollowMutation };

View File

@@ -1,14 +1,12 @@
import { InfiniteData, keepPreviousData, useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
import sumBy from 'lodash/sumBy';
import { type Chat, type ChatMessage as BaseChatMessage, type PaginatedResponse, chatMessageSchema, type Relationship } from 'pl-api';
import { type Chat, type ChatMessage as BaseChatMessage, type PaginatedResponse, chatMessageSchema } from 'pl-api';
import * as v from 'valibot';
import { importEntities } from 'pl-fe/actions/importer';
import { ChatWidgetScreens, useChatContext } from 'pl-fe/contexts/chat-context';
import { useStatContext } from 'pl-fe/contexts/stat-context';
import { Entities } from 'pl-fe/entity-store/entities';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useClient } from 'pl-fe/hooks/use-client';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
@@ -17,6 +15,7 @@ import { type ChatMessage, normalizeChatMessage } from 'pl-fe/normalizers/chat-m
import { reOrderChatListItems } from 'pl-fe/utils/chats';
import { flattenPages, updatePageItem } from 'pl-fe/utils/queries';
import { useRelationshipQuery } from './accounts/use-relationship';
import { queryClient } from './client';
import { useFetchRelationships } from './relationships';
@@ -27,7 +26,7 @@ const ChatKeys = {
const useChatMessages = (chat: Chat) => {
const client = useClient();
const isBlocked = useAppSelector((state) => (state.entities[Entities.RELATIONSHIPS]?.store[chat.account.id] as Relationship)?.blocked_by);
const isBlocked = !!useRelationshipQuery(chat?.account.id).data?.blocked_by;
const getChatMessages = async (chatId: string, pageParam?: Pick<PaginatedResponse<BaseChatMessage>, 'next'>) => {
const response = await (pageParam?.next ? pageParam.next() : client.chats.getChatMessages(chatId));