From 9c202a5193f16af944d8fca6a434dd2783b94200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Mon, 9 Feb 2026 19:13:02 +0100 Subject: [PATCH] pl-fe: ? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../chats/components/chat-list-item.tsx | 8 +- .../features/chats/components/chat-list.tsx | 16 ++-- .../chats/components/chat-message-list.tsx | 28 +++---- .../chats/components/chat-message.tsx | 23 ++---- .../chats/components/chat-pane/chat-pane.tsx | 6 +- .../components/chat-widget/chat-settings.tsx | 4 +- .../src/features/chats/components/chat.tsx | 15 +--- .../chats-page/components/chats-page-chat.tsx | 4 +- .../components/chats-page-sidebar.tsx | 6 +- packages/pl-fe/src/queries/chats.ts | 78 +++++++++++-------- packages/pl-fe/src/styles/new/components.scss | 1 + 11 files changed, 93 insertions(+), 96 deletions(-) diff --git a/packages/pl-fe/src/features/chats/components/chat-list-item.tsx b/packages/pl-fe/src/features/chats/components/chat-list-item.tsx index ebf51919b..0c4eecb27 100644 --- a/packages/pl-fe/src/features/chats/components/chat-list-item.tsx +++ b/packages/pl-fe/src/features/chats/components/chat-list-item.tsx @@ -13,7 +13,7 @@ import { useChatContext } from '@/contexts/chat-context'; import Emojify from '@/features/emoji/emojify'; import { useFeatures } from '@/hooks/use-features'; import { useRelationshipQuery } from '@/queries/accounts/use-relationship'; -import { useChatActions } from '@/queries/chats'; +import { useDeleteChat } from '@/queries/chats'; import { useModalsActions } from '@/stores/modals'; import type { Menu } from '@/components/dropdown-menu'; @@ -32,14 +32,14 @@ interface IChatListItemInterface { onClick: (chat: Chat) => void; } -const ChatListItem: React.FC = ({ chat, onClick }) => { +const ChatListItem: React.FC = React.memo(({ chat, onClick }) => { const { openModal } = useModalsActions(); const intl = useIntl(); const features = useFeatures(); const navigate = useNavigate(); const { isUsingMainChatPage } = useChatContext(); - const { deleteChat } = useChatActions(chat?.id as string); + const deleteChat = useDeleteChat(chat?.id as string); const { data: relationship } = useRelationshipQuery(chat?.account.id); const isBlocked = relationship?.blocked_by && false; @@ -159,6 +159,6 @@ const ChatListItem: React.FC = ({ chat, onClick }) => { ); -}; +}); export { ChatListItem as default }; diff --git a/packages/pl-fe/src/features/chats/components/chat-list.tsx b/packages/pl-fe/src/features/chats/components/chat-list.tsx index 94cb2e06c..507971fcd 100644 --- a/packages/pl-fe/src/features/chats/components/chat-list.tsx +++ b/packages/pl-fe/src/features/chats/components/chat-list.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { Virtuoso } from 'react-virtuoso'; import PullToRefresh from '@/components/pull-to-refresh'; @@ -37,6 +37,14 @@ const ChatList: React.FC = ({ onClickChat, useWindowScroll = false }) const handleRefresh = () => refetch(); + const renderChatListItem = useCallback((_index: number, chat: Chat | 'shoutbox') => { + if (chat === 'shoutbox') { + return
; + } + + return
; + }, [onClickChat]); + const renderEmpty = () => { if (isFetching) { return ( @@ -65,11 +73,7 @@ const ChatList: React.FC = ({ onClickChat, useWindowScroll = false }) useWindowScroll={useWindowScroll} data={allChats} endReached={handleLoadMore} - itemContent={(_index, chat) => ( -
- {chat === 'shoutbox' ? : } -
- )} + itemContent={renderChatListItem} components={{ ScrollSeekPlaceholder: PlaceholderChat, Footer: () => hasNextPage ? : null, diff --git a/packages/pl-fe/src/features/chats/components/chat-message-list.tsx b/packages/pl-fe/src/features/chats/components/chat-message-list.tsx index cb18e3c7b..f0d5bd366 100644 --- a/packages/pl-fe/src/features/chats/components/chat-message-list.tsx +++ b/packages/pl-fe/src/features/chats/components/chat-message-list.tsx @@ -10,7 +10,7 @@ import Stack from '@/components/ui/stack'; import Text from '@/components/ui/text'; import PlaceholderChatMessage from '@/features/placeholder/components/placeholder-chat-message'; import { useRelationshipQuery } from '@/queries/accounts/use-relationship'; -import { useChatActions, useChatMessages } from '@/queries/chats'; +import { useChatMessages, useMarkChatAsRead } from '@/queries/chats'; import ChatMessage from './chat-message'; @@ -67,13 +67,13 @@ interface IChatMessageList { } /** Scrollable list of chat messages. */ -const ChatMessageList: React.FC = ({ chat }) => { +const ChatMessageList: React.FC = React.memo(({ chat }) => { const intl = useIntl(); const node = useRef(null); const [firstItemIndex, setFirstItemIndex] = useState(START_INDEX - 20); - const { markChatAsRead } = useChatActions(chat.id); + const markChatAsRead = useMarkChatAsRead(chat.id); const { data: chatMessages, fetchNextPage, @@ -140,7 +140,7 @@ const ChatMessageList: React.FC = ({ chat }) => { return acc; }, []); }; - const cachedChatMessages = buildCachedMessages(); + const cachedChatMessages = useMemo(() => buildCachedMessages(), [chatMessages]); const initialScrollPositionProps = useMemo(() => { if (process.env.NODE_ENV === 'test') { @@ -160,7 +160,13 @@ const ChatMessageList: React.FC = ({ chat }) => { return false; }, [firstItemIndex, hasNextPage, isFetching]); - const renderDivider = (key: React.Key, text: string) => ; + const renderChatMessage = useCallback((index: number, chatMessage: ChatMessageEntity | { type: 'divider'; text: string }) => { + if ('type' in chatMessage && chatMessage.type === 'divider') { + return ; + } + + return ; + }, [chat]); useEffect(() => { const lastMessage = formattedChatMessages[formattedChatMessages.length - 1]; @@ -177,7 +183,7 @@ const ChatMessageList: React.FC = ({ chat }) => { * 2) it has not already been read */ if (!isMessagePending) { - markChatAsRead(lastMessageId); + markChatAsRead.mutate(lastMessageId); } }, [formattedChatMessages.length]); @@ -245,13 +251,7 @@ const ChatMessageList: React.FC = ({ chat }) => { data={cachedChatMessages} startReached={handleStartReached} followOutput='auto' - itemContent={(index, chatMessage) => { - if (chatMessage.type === 'divider') { - return renderDivider(index, (chatMessage as any).text); - } else { - return ; - } - }} + itemContent={(index, chatMessage) => renderChatMessage(index, chatMessage)} components={{ List, Scroller, @@ -267,6 +267,6 @@ const ChatMessageList: React.FC = ({ chat }) => { ); -}; +}); export { ChatMessageList as default, List as ChatMessageListList, Scroller as ChatMessageListScroller }; diff --git a/packages/pl-fe/src/features/chats/components/chat-message.tsx b/packages/pl-fe/src/features/chats/components/chat-message.tsx index 0728d6489..a9872edf9 100644 --- a/packages/pl-fe/src/features/chats/components/chat-message.tsx +++ b/packages/pl-fe/src/features/chats/components/chat-message.tsx @@ -1,4 +1,3 @@ -import { useMutation } from '@tanstack/react-query'; import clsx from 'clsx'; import escape from 'lodash/escape'; import React, { useMemo, useState } from 'react'; @@ -12,8 +11,7 @@ import Stack from '@/components/ui/stack'; import Text from '@/components/ui/text'; import { MediaGallery } from '@/features/ui/util/async-components'; import { useAppSelector } from '@/hooks/use-app-selector'; -import { ChatKeys, useChatActions } from '@/queries/chats'; -import { queryClient } from '@/queries/client'; +import { useDeleteChatMessage } from '@/queries/chats'; import { useModalsActions } from '@/stores/modals'; import { stripHTML } from '@/utils/html'; import { onlyEmoji } from '@/utils/rich-content'; @@ -46,26 +44,17 @@ interface IChatMessage { chatMessage: ChatMessageEntity; } -const ChatMessage = (props: IChatMessage) => { +const ChatMessage: React.FC = React.memo((props) => { const { chat, chatMessage } = props; const { openModal } = useModalsActions(); const intl = useIntl(); const me = useAppSelector((state) => state.me); - const { deleteChatMessage } = useChatActions(chat.id); + const deleteChatMessage = useDeleteChatMessage(chat.id); const [isMenuOpen, setIsMenuOpen] = useState(false); - const handleDeleteMessage = useMutation({ - mutationFn: (chatMessageId: string) => deleteChatMessage(chatMessageId), - onSettled: () => { - queryClient.invalidateQueries({ - queryKey: ChatKeys.chatMessages(chat.id), - }); - }, - }); - const content = parseContent(chatMessage); const isMyMessage = chatMessage.account_id === me; @@ -137,14 +126,14 @@ const ChatMessage = (props: IChatMessage) => { if (isMyMessage) { menu.push({ text: intl.formatMessage(messages.delete), - action: () => handleDeleteMessage.mutate(chatMessage.id), + action: () => deleteChatMessage.mutate(chatMessage.id), icon: require('@phosphor-icons/core/regular/trash.svg'), destructive: true, }); } else { menu.push({ text: intl.formatMessage(messages.deleteForMe), - action: () => handleDeleteMessage.mutate(chatMessage.id), + action: () => deleteChatMessage.mutate(chatMessage.id), icon: require('@phosphor-icons/core/regular/trash.svg'), destructive: true, }); @@ -273,6 +262,6 @@ const ChatMessage = (props: IChatMessage) => { ); -}; +}); export { ChatMessage as default }; diff --git a/packages/pl-fe/src/features/chats/components/chat-pane/chat-pane.tsx b/packages/pl-fe/src/features/chats/components/chat-pane/chat-pane.tsx index 42c3c79b9..67b654c94 100644 --- a/packages/pl-fe/src/features/chats/components/chat-pane/chat-pane.tsx +++ b/packages/pl-fe/src/features/chats/components/chat-pane/chat-pane.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; import { ChatWidgetScreens, useChatContext } from '@/contexts/chat-context'; @@ -25,13 +25,13 @@ const ChatPane = () => { const { screen, changeScreen, isOpen, toggleChatPane } = useChatContext(); const { chatsQuery: { data: chats, isLoading } } = useChats(); - const handleClickChat = (nextChat: Chat | 'shoutbox') => { + const handleClickChat = useCallback((nextChat: Chat | 'shoutbox') => { if (nextChat === 'shoutbox') { changeScreen(ChatWidgetScreens.SHOUTBOX); } else { changeScreen(ChatWidgetScreens.CHAT, nextChat.id); } - }; + }, [changeScreen]); const renderBody = () => { if (Number(chats?.length) > 0 || showShoutbox || isLoading) { diff --git a/packages/pl-fe/src/features/chats/components/chat-widget/chat-settings.tsx b/packages/pl-fe/src/features/chats/components/chat-widget/chat-settings.tsx index f3b5b872f..9e2c712ab 100644 --- a/packages/pl-fe/src/features/chats/components/chat-widget/chat-settings.tsx +++ b/packages/pl-fe/src/features/chats/components/chat-widget/chat-settings.tsx @@ -10,7 +10,7 @@ import Text from '@/components/ui/text'; import { ChatWidgetScreens, useChatContext } from '@/contexts/chat-context'; import { useFeatures } from '@/hooks/use-features'; import { useUnblockAccountMutation, useRelationshipQuery } from '@/queries/accounts/use-relationship'; -import { useChatActions } from '@/queries/chats'; +import { useDeleteChat } from '@/queries/chats'; import { useModalsActions } from '@/stores/modals'; import ChatPaneHeader from './chat-pane-header'; @@ -30,7 +30,7 @@ const ChatSettings = () => { const { openModal } = useModalsActions(); const { chat, changeScreen, toggleChatPane } = useChatContext(); - const { deleteChat } = useChatActions(chat?.id as string); + const deleteChat = useDeleteChat(chat?.id as string); const { mutate: unblockAccount } = useUnblockAccountMutation(chat?.account.id!); diff --git a/packages/pl-fe/src/features/chats/components/chat.tsx b/packages/pl-fe/src/features/chats/components/chat.tsx index cf197c720..98fc468a8 100644 --- a/packages/pl-fe/src/features/chats/components/chat.tsx +++ b/packages/pl-fe/src/features/chats/components/chat.tsx @@ -5,7 +5,7 @@ import { defineMessages, useIntl } from 'react-intl'; import { uploadMedia } from '@/actions/media'; import Stack from '@/components/ui/stack'; import { useAppDispatch } from '@/hooks/use-app-dispatch'; -import { useChatActions } from '@/queries/chats'; +import { useCreateChatMessage } from '@/queries/chats'; import toast from '@/toast'; import ChatComposer from './chat-composer'; @@ -51,7 +51,7 @@ const Chat: React.FC = ({ chat, inputRef, className }) => { const intl = useIntl(); const dispatch = useAppDispatch(); - const { createChatMessage } = useChatActions(chat.id); + const createChatMessage = useCreateChatMessage(chat.id); const [content, setContent] = useState(''); const [attachment, setAttachment] = useState(null); @@ -99,8 +99,6 @@ const Chat: React.FC = ({ chat, inputRef, className }) => { const insertLine = () => setContent(content + '\n'); const handleKeyDown: React.KeyboardEventHandler = (event) => { - markRead(); - if (event.key === 'Enter' && event.shiftKey) { event.preventDefault(); insertLine(); @@ -120,13 +118,6 @@ const Chat: React.FC = ({ chat, inputRef, className }) => { } }; - const markRead = () => { - // markAsRead.mutate(); - // dispatch(markChatRead(chatId)); - }; - - const handleMouseOver = () => markRead(); - const handleRemoveFile = () => { setAttachment(null); setResetFileKey(fileKeyGen()); @@ -159,7 +150,7 @@ const Chat: React.FC = ({ chat, inputRef, className }) => { }, [chat.id, inputRef?.current]); return ( - +
diff --git a/packages/pl-fe/src/features/chats/components/chats-page/components/chats-page-chat.tsx b/packages/pl-fe/src/features/chats/components/chats-page/components/chats-page-chat.tsx index 38bc8822c..4eb263279 100644 --- a/packages/pl-fe/src/features/chats/components/chats-page/components/chats-page-chat.tsx +++ b/packages/pl-fe/src/features/chats/components/chats-page/components/chats-page-chat.tsx @@ -13,7 +13,7 @@ import VerificationBadge from '@/components/verification-badge'; import { chatRoute } from '@/features/ui/router'; import { useFeatures } from '@/hooks/use-features'; import { useUnblockAccountMutation, useRelationshipQuery } from '@/queries/accounts/use-relationship'; -import { useChat, useChatActions } from '@/queries/chats'; +import { useChat, useDeleteChat } from '@/queries/chats'; import { useModalsActions } from '@/stores/modals'; import Chat from '../../chat'; @@ -47,7 +47,7 @@ const ChatsPageChat = () => { const inputRef = useRef(null); - const { deleteChat } = useChatActions(chat?.id as string); + const deleteChat = useDeleteChat(chat?.id as string); const isBlocked = !!useRelationshipQuery(chat?.account.id).data?.blocked_by; diff --git a/packages/pl-fe/src/features/chats/components/chats-page/components/chats-page-sidebar.tsx b/packages/pl-fe/src/features/chats/components/chats-page/components/chats-page-sidebar.tsx index 71d92f57c..6ef67fa03 100644 --- a/packages/pl-fe/src/features/chats/components/chats-page/components/chats-page-sidebar.tsx +++ b/packages/pl-fe/src/features/chats/components/chats-page/components/chats-page-sidebar.tsx @@ -1,5 +1,5 @@ import { useNavigate } from '@tanstack/react-router'; -import React from 'react'; +import React, { useCallback } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { CardTitle } from '@/components/ui/card'; @@ -19,13 +19,13 @@ const ChatsPageSidebar = () => { const intl = useIntl(); const navigate = useNavigate(); - const handleClickChat = (chat: Chat | 'shoutbox') => { + const handleClickChat = useCallback((chat: Chat | 'shoutbox') => { if (chat === 'shoutbox') { navigate({ to: '/chats/shoutbox' }); } else { navigate({ to: '/chats/$chatId', params: { chatId: chat.id } }); } - }; + }, [navigate]); const handleChatCreate = () => { navigate({ to: '/chats/new' }); diff --git a/packages/pl-fe/src/queries/chats.ts b/packages/pl-fe/src/queries/chats.ts index bd67000ef..53121d450 100644 --- a/packages/pl-fe/src/queries/chats.ts +++ b/packages/pl-fe/src/queries/chats.ts @@ -1,7 +1,6 @@ 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 } from 'pl-api'; -import { useCallback, useMemo } from 'react'; import * as v from 'valibot'; import { importEntities } from '@/actions/importer'; @@ -121,36 +120,38 @@ const useChat = (chatId?: string) => { }); }; -const useChatActions = (chatId: string) => { - const { account } = useOwnAccount(); +const useMarkChatAsRead = (chatId: string) => { const client = useClient(); const { setUnreadChatsCount } = useStatContext(); - const { chat, changeScreen } = useChatContext(); + return useMutation({ + mutationFn: (lastReadId: string) => client.chats.markChatAsRead(chatId, lastReadId), + onSuccess: (data) => { + updatePageItem(['chats', 'search'], data, (o, n) => o.id === n.id); + const queryData = queryClient.getQueryData>>(['chats', 'search']); - const markChatAsRead = async (lastReadId: string) => - client.chats.markChatAsRead(chatId, lastReadId) - .then((data) => { - updatePageItem(['chats', 'search'], data, (o, n) => o.id === n.id); - const queryData = queryClient.getQueryData>>(['chats', 'search']); + if (queryData) { + const flattenedQueryData: any = flattenPages(queryData)?.map((chat: any) => { + if (chat.id === data.id) { + return data; + } else { + return chat; + } + }); + setUnreadChatsCount(sumBy(flattenedQueryData, (chat: Chat) => chat.unread)); + } + }, + }); +}; - if (queryData) { - const flattenedQueryData: any = flattenPages(queryData)?.map((chat: any) => { - if (chat.id === data.id) { - return data; - } else { - return chat; - } - }); - setUnreadChatsCount(sumBy(flattenedQueryData, (chat: Chat) => chat.unread)); - } +const useCreateChatMessage = (chatId: string) => { + const { account } = useOwnAccount(); + const client = useClient(); - return data; - }) - .catch(() => null); + const { chat } = useChatContext(); - const createChatMessage = useMutation({ + return useMutation({ mutationFn: ({ chatId, content, mediaId }: { chatId: string; content: string; mediaId?: string }) => client.chats.createChatMessage(chatId, { content, media_id: mediaId }), retry: false, @@ -212,9 +213,14 @@ const useChatActions = (chatId: string) => { reOrderChatListItems(); }, }); - const deleteChatMessage = useCallback((chatMessageId: string) => client.chats.deleteChatMessage(chatId, chatMessageId), [chatId]); +}; - const deleteChat = useMutation({ +const useDeleteChat = (chatId: string) => { + const client = useClient(); + + const { changeScreen } = useChatContext(); + + return useMutation({ mutationFn: () => client.chats.deleteChat(chatId), onSuccess() { changeScreen(ChatWidgetScreens.INBOX); @@ -222,13 +228,19 @@ const useChatActions = (chatId: string) => { queryClient.invalidateQueries({ queryKey: ['chats', 'search'] }); }, }); - - return useMemo(() => ({ - createChatMessage, - deleteChat, - deleteChatMessage, - markChatAsRead, - }), [createChatMessage, deleteChat, deleteChatMessage, markChatAsRead]); }; -export { ChatKeys, useChat, useChatActions, useChats, useChatMessages }; +const useDeleteChatMessage = (chatId: string) => { + const client = useClient(); + + return useMutation({ + mutationFn: (chatMessageId: string) => client.chats.deleteChatMessage(chatId, chatMessageId), + onSettled: () => { + queryClient.invalidateQueries({ + queryKey: ChatKeys.chatMessages(chatId), + }); + }, + }); +}; + +export { ChatKeys, useChat, useMarkChatAsRead, useCreateChatMessage, useDeleteChat, useDeleteChatMessage, useChats, useChatMessages }; diff --git a/packages/pl-fe/src/styles/new/components.scss b/packages/pl-fe/src/styles/new/components.scss index f87ac8614..afe2954a6 100644 --- a/packages/pl-fe/src/styles/new/components.scss +++ b/packages/pl-fe/src/styles/new/components.scss @@ -208,6 +208,7 @@ @apply py-1.5 pr-4 rtl:pl-4 rtl:pr-0; > :first-child { + word-break: break-word; @apply text-gray-900 dark:text-gray-100; } }