2023-10-17 13:19:05 -07:00
|
|
|
import { InfiniteData, keepPreviousData, useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
|
2022-09-27 13:05:19 -07:00
|
|
|
import sumBy from 'lodash/sumBy';
|
2024-08-11 01:48:58 -07:00
|
|
|
import { type Chat, type ChatMessage as BaseChatMessage, type PaginatedResponse, chatMessageSchema } from 'pl-api';
|
2022-08-10 05:38:49 -07:00
|
|
|
|
2022-09-29 10:36:35 -07:00
|
|
|
import { importFetchedAccount, importFetchedAccounts } from 'soapbox/actions/importer';
|
2022-11-02 12:28:16 -07:00
|
|
|
import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context';
|
2022-09-27 13:05:19 -07:00
|
|
|
import { useStatContext } from 'soapbox/contexts/stat-context';
|
2024-08-04 07:09:52 -07:00
|
|
|
import { useAppDispatch, useAppSelector, useClient, useFeatures, useLoggedIn, useOwnAccount } from 'soapbox/hooks';
|
2024-08-11 01:48:58 -07:00
|
|
|
import { type ChatMessage, normalizeChatMessage } from 'soapbox/normalizers';
|
2024-04-28 05:50:23 -07:00
|
|
|
import { reOrderChatListItems } from 'soapbox/utils/chats';
|
2024-08-07 07:23:44 -07:00
|
|
|
import { flattenPages, updatePageItem } from 'soapbox/utils/queries';
|
2022-08-10 05:38:49 -07:00
|
|
|
|
2022-08-16 10:38:17 -07:00
|
|
|
import { queryClient } from './client';
|
2022-11-10 08:56:36 -08:00
|
|
|
import { useFetchRelationships } from './relationships';
|
2022-08-16 10:38:17 -07:00
|
|
|
|
2022-10-05 12:15:16 -07:00
|
|
|
const ChatKeys = {
|
2022-09-28 13:38:05 -07:00
|
|
|
chat: (chatId?: string) => ['chats', 'chat', chatId] as const,
|
2022-09-27 12:42:24 -07:00
|
|
|
chatMessages: (chatId: string) => ['chats', 'messages', chatId] as const,
|
2022-11-01 06:44:21 -07:00
|
|
|
};
|
|
|
|
|
2024-08-07 07:23:44 -07:00
|
|
|
const useChatMessages = (chat: Chat) => {
|
2024-08-04 07:09:52 -07:00
|
|
|
const client = useClient();
|
2022-10-25 10:10:53 -07:00
|
|
|
const isBlocked = useAppSelector((state) => state.getIn(['relationships', chat.account.id, 'blocked_by']));
|
2022-08-10 05:38:49 -07:00
|
|
|
|
2024-08-07 07:23:44 -07:00
|
|
|
const getChatMessages = async (chatId: string, pageParam?: Pick<PaginatedResponse<BaseChatMessage>, 'next'>) => {
|
2024-08-06 10:52:36 -07:00
|
|
|
const response = await (pageParam?.next ? pageParam.next() : client.chats.getChatMessages(chatId));
|
|
|
|
|
2024-08-07 07:23:44 -07:00
|
|
|
return {
|
|
|
|
...response,
|
2024-08-11 01:48:58 -07:00
|
|
|
items: response.items.map(normalizeChatMessage),
|
2024-08-07 07:23:44 -07:00
|
|
|
};
|
2022-08-10 05:38:49 -07:00
|
|
|
};
|
|
|
|
|
2023-10-17 13:19:05 -07:00
|
|
|
const queryInfo = useInfiniteQuery({
|
|
|
|
queryKey: ChatKeys.chatMessages(chat.id),
|
|
|
|
queryFn: ({ pageParam }) => getChatMessages(chat.id, pageParam),
|
2022-10-25 10:10:53 -07:00
|
|
|
enabled: !isBlocked,
|
2023-10-17 13:19:05 -07:00
|
|
|
gcTime: 0,
|
2022-11-03 10:55:33 -07:00
|
|
|
staleTime: 0,
|
2024-08-07 07:23:44 -07:00
|
|
|
initialPageParam: { next: null as (() => Promise<PaginatedResponse<BaseChatMessage>>) | null },
|
2024-08-06 10:52:36 -07:00
|
|
|
getNextPageParam: (config) => config,
|
2022-08-10 05:38:49 -07:00
|
|
|
});
|
|
|
|
|
2024-08-11 01:48:58 -07:00
|
|
|
const data = flattenPages<ChatMessage>(queryInfo.data as any)?.toReversed();
|
2022-08-10 05:38:49 -07:00
|
|
|
|
|
|
|
return {
|
|
|
|
...queryInfo,
|
|
|
|
data,
|
|
|
|
};
|
|
|
|
};
|
2022-08-26 09:41:25 -07:00
|
|
|
|
2024-04-28 05:50:23 -07:00
|
|
|
const useChats = () => {
|
2024-08-04 07:09:52 -07:00
|
|
|
const client = useClient();
|
2022-09-08 09:47:19 -07:00
|
|
|
const dispatch = useAppDispatch();
|
2022-09-22 14:04:52 -07:00
|
|
|
const features = useFeatures();
|
2022-09-27 13:05:19 -07:00
|
|
|
const { setUnreadChatsCount } = useStatContext();
|
2022-11-10 08:56:36 -08:00
|
|
|
const fetchRelationships = useFetchRelationships();
|
2024-07-21 03:48:57 -07:00
|
|
|
const { me } = useLoggedIn();
|
2022-08-10 05:38:49 -07:00
|
|
|
|
2024-08-06 10:52:36 -07:00
|
|
|
const getChats = async (pageParam?: Pick<PaginatedResponse<Chat>, 'next'>): Promise<PaginatedResponse<Chat>> => {
|
|
|
|
const response = await (pageParam?.next || client.chats.getChats)();
|
|
|
|
const { items } = response;
|
2022-08-26 09:41:25 -07:00
|
|
|
|
2024-08-06 10:52:36 -07:00
|
|
|
setUnreadChatsCount(sumBy(data, (chat) => chat.unread));
|
2022-09-27 13:05:19 -07:00
|
|
|
|
2022-09-08 09:47:19 -07:00
|
|
|
// Set the relationships to these users in the redux store.
|
2024-08-06 10:52:36 -07:00
|
|
|
fetchRelationships.mutate({ accountIds: items.map((item) => item.account.id) });
|
|
|
|
dispatch(importFetchedAccounts(items.map((item) => item.account)));
|
|
|
|
|
|
|
|
return response;
|
2022-08-10 05:38:49 -07:00
|
|
|
};
|
|
|
|
|
2023-10-17 13:19:05 -07:00
|
|
|
const queryInfo = useInfiniteQuery({
|
2024-04-28 05:50:23 -07:00
|
|
|
queryKey: ['chats', 'search'],
|
2023-10-17 13:19:05 -07:00
|
|
|
queryFn: ({ pageParam }) => getChats(pageParam),
|
|
|
|
placeholderData: keepPreviousData,
|
2024-07-21 03:48:57 -07:00
|
|
|
enabled: features.chats && !!me,
|
2024-08-06 10:52:36 -07:00
|
|
|
initialPageParam: { next: null as (() => Promise<PaginatedResponse<Chat>>) | null },
|
|
|
|
getNextPageParam: (config) => config,
|
2022-08-10 05:38:49 -07:00
|
|
|
});
|
|
|
|
|
2022-12-06 13:12:24 -08:00
|
|
|
const data = flattenPages(queryInfo.data);
|
2022-08-26 09:41:25 -07:00
|
|
|
|
|
|
|
const chatsQuery = {
|
|
|
|
...queryInfo,
|
|
|
|
data,
|
|
|
|
};
|
|
|
|
|
2024-08-04 07:09:52 -07:00
|
|
|
const getOrCreateChatByAccountId = (accountId: string) =>
|
2024-08-06 10:52:36 -07:00
|
|
|
client.chats.createChat(accountId);
|
2022-08-10 05:38:49 -07:00
|
|
|
|
|
|
|
return { chatsQuery, getOrCreateChatByAccountId };
|
|
|
|
};
|
|
|
|
|
2022-09-28 13:38:05 -07:00
|
|
|
const useChat = (chatId?: string) => {
|
2024-08-04 07:09:52 -07:00
|
|
|
const client = useClient();
|
2022-09-29 10:36:35 -07:00
|
|
|
const dispatch = useAppDispatch();
|
2022-11-10 08:56:36 -08:00
|
|
|
const fetchRelationships = useFetchRelationships();
|
2022-09-28 13:38:05 -07:00
|
|
|
|
|
|
|
const getChat = async () => {
|
|
|
|
if (chatId) {
|
2024-08-06 10:52:36 -07:00
|
|
|
const data = await client.chats.getChat(chatId);
|
2022-09-29 10:36:35 -07:00
|
|
|
|
2022-11-10 08:56:36 -08:00
|
|
|
fetchRelationships.mutate({ accountIds: [data.account.id] });
|
2022-09-29 10:36:35 -07:00
|
|
|
dispatch(importFetchedAccount(data.account));
|
|
|
|
|
2022-09-28 13:38:05 -07:00
|
|
|
return data;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-08-06 10:52:36 -07:00
|
|
|
return useQuery<Chat | undefined>({
|
2023-10-17 13:19:05 -07:00
|
|
|
queryKey: ChatKeys.chat(chatId),
|
|
|
|
queryFn: getChat,
|
|
|
|
gcTime: 0,
|
2022-11-02 12:28:16 -07:00
|
|
|
enabled: !!chatId,
|
|
|
|
});
|
2022-09-28 13:38:05 -07:00
|
|
|
};
|
|
|
|
|
2022-09-28 13:20:59 -07:00
|
|
|
const useChatActions = (chatId: string) => {
|
2023-06-25 10:35:09 -07:00
|
|
|
const { account } = useOwnAccount();
|
2024-08-04 07:09:52 -07:00
|
|
|
const client = useClient();
|
2022-11-09 09:24:44 -08:00
|
|
|
|
2022-10-27 10:59:54 -07:00
|
|
|
const { setUnreadChatsCount } = useStatContext();
|
2022-10-26 10:08:02 -07:00
|
|
|
|
2022-11-02 12:28:16 -07:00
|
|
|
const { chat, changeScreen } = useChatContext();
|
2022-08-10 05:38:49 -07:00
|
|
|
|
2024-05-12 16:18:04 -07:00
|
|
|
const markChatAsRead = async (lastReadId: string) =>
|
2024-08-06 10:52:36 -07:00
|
|
|
client.chats.markChatAsRead(chatId, lastReadId)
|
|
|
|
.then((data) => {
|
2024-04-28 05:50:23 -07:00
|
|
|
updatePageItem(['chats', 'search'], data, (o, n) => o.id === n.id);
|
2024-08-07 07:23:44 -07:00
|
|
|
const queryData = queryClient.getQueryData<InfiniteData<PaginatedResponse<unknown>>>(['chats', 'search']);
|
2022-10-28 10:01:39 -07:00
|
|
|
|
2022-10-27 10:59:54 -07:00
|
|
|
if (queryData) {
|
|
|
|
const flattenedQueryData: any = flattenPages(queryData)?.map((chat: any) => {
|
|
|
|
if (chat.id === data.id) {
|
|
|
|
return data;
|
|
|
|
} else {
|
|
|
|
return chat;
|
|
|
|
}
|
|
|
|
});
|
2024-08-07 07:23:44 -07:00
|
|
|
setUnreadChatsCount(sumBy(flattenedQueryData, (chat: Chat) => chat.unread));
|
2022-10-27 10:59:54 -07:00
|
|
|
}
|
2022-10-28 10:01:39 -07:00
|
|
|
|
|
|
|
return data;
|
2022-10-27 10:59:54 -07:00
|
|
|
})
|
2022-08-30 07:10:31 -07:00
|
|
|
.catch(() => null);
|
2022-08-10 05:38:49 -07:00
|
|
|
|
2023-10-17 13:19:05 -07:00
|
|
|
const createChatMessage = useMutation({
|
2024-08-04 07:09:52 -07:00
|
|
|
mutationFn: ({ chatId, content, mediaId }: { chatId: string; content: string; mediaId?: string }) =>
|
2024-08-06 10:52:36 -07:00
|
|
|
client.chats.createChatMessage(chatId, { content, media_id: mediaId }),
|
2023-10-17 13:19:05 -07:00
|
|
|
retry: false,
|
|
|
|
onMutate: async (variables) => {
|
|
|
|
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
|
|
|
|
await queryClient.cancelQueries({
|
|
|
|
queryKey: ['chats', 'messages', variables.chatId],
|
|
|
|
});
|
2022-11-09 09:24:44 -08:00
|
|
|
|
2023-10-17 13:19:05 -07:00
|
|
|
// Snapshot the previous value
|
|
|
|
const prevContent = variables.content;
|
|
|
|
const prevChatMessages = queryClient.getQueryData(['chats', 'messages', variables.chatId]);
|
|
|
|
const pendingId = String(Number(new Date()));
|
2022-11-09 09:24:44 -08:00
|
|
|
|
2023-10-17 13:19:05 -07:00
|
|
|
// Optimistically update to the new value
|
|
|
|
queryClient.setQueryData(ChatKeys.chatMessages(variables.chatId), (prevResult: any) => {
|
|
|
|
const newResult = { ...prevResult };
|
|
|
|
newResult.pages = newResult.pages.map((page: any, idx: number) => {
|
|
|
|
if (idx === 0) {
|
|
|
|
return {
|
|
|
|
...page,
|
|
|
|
result: [
|
|
|
|
normalizeChatMessage({
|
2024-08-11 01:48:58 -07:00
|
|
|
...chatMessageSchema.parse({
|
|
|
|
content: variables.content,
|
|
|
|
id: pendingId,
|
|
|
|
created_at: new Date(),
|
|
|
|
account_id: account?.id,
|
|
|
|
unread: true,
|
|
|
|
}),
|
2023-10-17 13:19:05 -07:00
|
|
|
pending: true,
|
|
|
|
}),
|
|
|
|
...page.result,
|
|
|
|
],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return page;
|
2022-11-09 09:24:44 -08:00
|
|
|
});
|
|
|
|
|
2023-10-17 13:19:05 -07:00
|
|
|
return newResult;
|
|
|
|
});
|
|
|
|
|
|
|
|
return { prevChatMessages, prevContent, pendingId };
|
2022-11-09 09:24:44 -08:00
|
|
|
},
|
2023-10-17 13:19:05 -07:00
|
|
|
// If the mutation fails, use the context returned from onMutate to roll back
|
|
|
|
onError: (_error: any, variables, context: any) => {
|
|
|
|
queryClient.setQueryData(['chats', 'messages', variables.chatId], context.prevChatMessages);
|
|
|
|
},
|
|
|
|
onSuccess: (response: any, variables, context) => {
|
2024-08-06 10:52:36 -07:00
|
|
|
const nextChat = { ...chat, last_message: response };
|
2024-04-28 05:50:23 -07:00
|
|
|
updatePageItem(['chats', 'search'], nextChat, (o, n) => o.id === n.id);
|
2023-10-17 13:19:05 -07:00
|
|
|
updatePageItem(
|
|
|
|
ChatKeys.chatMessages(variables.chatId),
|
2024-08-06 10:52:36 -07:00
|
|
|
normalizeChatMessage(response),
|
2023-10-17 13:19:05 -07:00
|
|
|
(o) => o.id === context.pendingId,
|
|
|
|
);
|
|
|
|
reOrderChatListItems();
|
|
|
|
},
|
|
|
|
});
|
2024-05-11 14:37:37 -07:00
|
|
|
const deleteChatMessage = (chatMessageId: string) =>
|
2024-08-06 10:52:36 -07:00
|
|
|
client.chats.deleteChatMessage(chatId, chatMessageId);
|
2022-08-16 05:39:58 -07:00
|
|
|
|
2023-10-17 13:19:05 -07:00
|
|
|
const deleteChat = useMutation({
|
2024-08-06 10:52:36 -07:00
|
|
|
mutationFn: () => client.chats.deleteChat(chatId),
|
2022-11-02 12:28:16 -07:00
|
|
|
onSuccess() {
|
|
|
|
changeScreen(ChatWidgetScreens.INBOX);
|
2023-10-17 13:19:05 -07:00
|
|
|
queryClient.invalidateQueries({ queryKey: ChatKeys.chatMessages(chatId) });
|
2024-04-28 05:50:23 -07:00
|
|
|
queryClient.invalidateQueries({ queryKey: ['chats', 'search'] });
|
2023-02-08 09:58:01 -08:00
|
|
|
},
|
2023-10-17 13:19:05 -07:00
|
|
|
});
|
2023-02-08 09:58:01 -08:00
|
|
|
|
|
|
|
return {
|
|
|
|
createChatMessage,
|
|
|
|
deleteChat,
|
|
|
|
deleteChatMessage,
|
|
|
|
markChatAsRead,
|
|
|
|
};
|
2022-08-10 05:38:49 -07:00
|
|
|
};
|
|
|
|
|
2024-08-11 01:48:58 -07:00
|
|
|
export { ChatKeys, useChat, useChatActions, useChats, useChatMessages };
|