Refactor ChatSearch and add various states
This commit is contained in:
parent
0952fe6dae
commit
0ae515ef18
|
@ -15,7 +15,7 @@ import useAccountSearch from 'soapbox/queries/search';
|
||||||
|
|
||||||
import ChatList from '../chat-list';
|
import ChatList from '../chat-list';
|
||||||
import ChatPaneHeader from '../chat-pane-header';
|
import ChatPaneHeader from '../chat-pane-header';
|
||||||
import ChatSearch from '../chat-search';
|
import ChatSearch from '../chat-search/chat-search';
|
||||||
import ChatWindow from '../chat-window';
|
import ChatWindow from '../chat-window';
|
||||||
import { Pane } from '../ui';
|
import { Pane } from '../ui';
|
||||||
|
|
||||||
|
@ -44,10 +44,11 @@ const ChatPane = () => {
|
||||||
const unreadCount = sumBy(chats, (chat) => chat.unread);
|
const unreadCount = sumBy(chats, (chat) => chat.unread);
|
||||||
|
|
||||||
const hasSearchValue = Number(value?.length) > 0;
|
const hasSearchValue = Number(value?.length) > 0;
|
||||||
console.log('hasSearchValue', hasSearchValue);
|
|
||||||
|
|
||||||
|
const handleClickChat = (chat: IChat) => {
|
||||||
const handleClickChat = (chat: IChat) => setChat(chat);
|
setChat(chat);
|
||||||
|
setValue(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
const clearValue = () => {
|
const clearValue = () => {
|
||||||
if (hasSearchValue) {
|
if (hasSearchValue) {
|
||||||
|
@ -133,7 +134,10 @@ const ChatPane = () => {
|
||||||
unreadCount={unreadCount}
|
unreadCount={unreadCount}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onToggle={toggleChatPane}
|
onToggle={toggleChatPane}
|
||||||
secondaryAction={() => setSearching(true)}
|
secondaryAction={() => {
|
||||||
|
setSearching(true);
|
||||||
|
setValue(undefined);
|
||||||
|
}}
|
||||||
secondaryActionIcon={require('@tabler/icons/edit.svg')}
|
secondaryActionIcon={require('@tabler/icons/edit.svg')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import React from 'react';
|
||||||
import { __stub } from 'soapbox/api';
|
import { __stub } from 'soapbox/api';
|
||||||
import { ChatProvider } from 'soapbox/contexts/chat-context';
|
import { ChatProvider } from 'soapbox/contexts/chat-context';
|
||||||
|
|
||||||
import { render, screen, waitFor } from '../../../../jest/test-helpers';
|
import { render, screen, waitFor } from '../../../../../jest/test-helpers';
|
||||||
import ChatSearch from '../chat-search';
|
import ChatSearch from '../chat-search';
|
||||||
|
|
||||||
const renderComponent = () => render(
|
const renderComponent = () => render(
|
||||||
|
@ -28,7 +28,7 @@ describe('<ChatSearch />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the pane is open', () => {
|
describe('when the pane is open', () => {
|
||||||
beforeEach(async() => {
|
beforeEach(async () => {
|
||||||
renderComponent();
|
renderComponent();
|
||||||
await userEvent.click(screen.getByTestId('icon-button'));
|
await userEvent.click(screen.getByTestId('icon-button'));
|
||||||
});
|
});
|
||||||
|
@ -50,7 +50,7 @@ describe('<ChatSearch />', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders accounts', async() => {
|
it('renders accounts', async () => {
|
||||||
renderComponent();
|
renderComponent();
|
||||||
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Stack, Text } from 'soapbox/components/ui';
|
||||||
|
|
||||||
|
const Blankslate = () => (
|
||||||
|
<Stack justifyContent='center' alignItems='center' space={2} className='h-full w-2/3 mx-auto'>
|
||||||
|
<Text weight='bold' size='lg' align='center'>Search followers</Text>
|
||||||
|
<Text theme='muted' align='center'>
|
||||||
|
You can start a conversation with anyone that follows you.
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Blankslate;
|
|
@ -3,16 +3,19 @@ import { AxiosError } from 'axios';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import { Avatar, HStack, Icon, Input, Stack, Text } from 'soapbox/components/ui';
|
import { HStack, Icon, Input, Stack, Text } from 'soapbox/components/ui';
|
||||||
import VerificationBadge from 'soapbox/components/verification_badge';
|
|
||||||
import { useChatContext } from 'soapbox/contexts/chat-context';
|
import { useChatContext } from 'soapbox/contexts/chat-context';
|
||||||
import { useAppDispatch, useDebounce } from 'soapbox/hooks';
|
import { useAppDispatch, useDebounce } from 'soapbox/hooks';
|
||||||
import { useChats } from 'soapbox/queries/chats';
|
import { useChats } from 'soapbox/queries/chats';
|
||||||
import { queryClient } from 'soapbox/queries/client';
|
import { queryClient } from 'soapbox/queries/client';
|
||||||
import useAccountSearch from 'soapbox/queries/search';
|
import useAccountSearch from 'soapbox/queries/search';
|
||||||
|
|
||||||
import ChatPaneHeader from './chat-pane-header';
|
import ChatPaneHeader from '../chat-pane-header';
|
||||||
import { Pane } from './ui';
|
import { Pane } from '../ui';
|
||||||
|
|
||||||
|
import Blankslate from './blankslate';
|
||||||
|
import EmptyResultsBlankslate from './empty-results-blankslate';
|
||||||
|
import Results from './results';
|
||||||
|
|
||||||
const ChatSearch = () => {
|
const ChatSearch = () => {
|
||||||
const debounce = useDebounce;
|
const debounce = useDebounce;
|
||||||
|
@ -24,9 +27,10 @@ const ChatSearch = () => {
|
||||||
const [value, setValue] = useState<string>();
|
const [value, setValue] = useState<string>();
|
||||||
const debouncedValue = debounce(value as string, 300);
|
const debouncedValue = debounce(value as string, 300);
|
||||||
|
|
||||||
const { data: accounts } = useAccountSearch(debouncedValue);
|
const { data: accounts, isFetching } = useAccountSearch(debouncedValue);
|
||||||
|
|
||||||
const hasSearchValue = value && value.length > 0;
|
const hasSearchValue = debouncedValue && debouncedValue.length > 0;
|
||||||
|
const hasSearchResults = (accounts || []).length > 0;
|
||||||
|
|
||||||
const handleClickOnSearchResult = useMutation((accountId: string) => {
|
const handleClickOnSearchResult = useMutation((accountId: string) => {
|
||||||
return getOrCreateChatByAccountId(accountId);
|
return getOrCreateChatByAccountId(accountId);
|
||||||
|
@ -41,6 +45,24 @@ const ChatSearch = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const renderBody = () => {
|
||||||
|
if (hasSearchResults) {
|
||||||
|
return (
|
||||||
|
<Results
|
||||||
|
accounts={accounts}
|
||||||
|
onSelect={(id) => {
|
||||||
|
handleClickOnSearchResult.mutate(id);
|
||||||
|
clearValue();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (hasSearchValue && !hasSearchResults && !isFetching) {
|
||||||
|
return <EmptyResultsBlankslate />;
|
||||||
|
} else {
|
||||||
|
return <Blankslate />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const clearValue = () => {
|
const clearValue = () => {
|
||||||
if (hasSearchValue) {
|
if (hasSearchValue) {
|
||||||
setValue('');
|
setValue('');
|
||||||
|
@ -93,30 +115,7 @@ const ChatSearch = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Stack className='overflow-y-scroll flex-grow h-full' space={2}>
|
<Stack className='overflow-y-scroll flex-grow h-full' space={2}>
|
||||||
{(accounts || []).map((account: any) => (
|
{renderBody()}
|
||||||
<button
|
|
||||||
key={account.id}
|
|
||||||
type='button'
|
|
||||||
className='px-4 py-2 w-full flex flex-col hover:bg-gray-100 dark:hover:bg-gray-800'
|
|
||||||
onClick={() => {
|
|
||||||
handleClickOnSearchResult.mutate(account.id);
|
|
||||||
clearValue();
|
|
||||||
}}
|
|
||||||
data-testid='account'
|
|
||||||
>
|
|
||||||
<HStack alignItems='center' space={2}>
|
|
||||||
<Avatar src={account.avatar} size={40} />
|
|
||||||
|
|
||||||
<Stack alignItems='start'>
|
|
||||||
<div className='flex items-center space-x-1 flex-grow'>
|
|
||||||
<Text weight='bold' size='sm' truncate>{account.display_name}</Text>
|
|
||||||
{account.verified && <VerificationBadge />}
|
|
||||||
</div>
|
|
||||||
<Text size='sm' weight='medium' theme='muted' truncate>@{account.acct}</Text>
|
|
||||||
</Stack>
|
|
||||||
</HStack>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
) : null}
|
) : null}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Stack, Text } from 'soapbox/components/ui';
|
||||||
|
|
||||||
|
const EmptyResultsBlankslate = () => (
|
||||||
|
<Stack justifyContent='center' alignItems='center' space={2} className='h-full w-2/3 mx-auto'>
|
||||||
|
<Text weight='bold' size='lg' align='center'>No matches found</Text>
|
||||||
|
<Text theme='muted' align='center'>
|
||||||
|
Try searching for another name.
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default EmptyResultsBlankslate;
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Avatar, HStack, Stack, Text } from 'soapbox/components/ui';
|
||||||
|
import VerificationBadge from 'soapbox/components/verification_badge';
|
||||||
|
|
||||||
|
interface IResults {
|
||||||
|
accounts: {
|
||||||
|
display_name: string
|
||||||
|
acct: string
|
||||||
|
id: string
|
||||||
|
avatar: string
|
||||||
|
verified: boolean
|
||||||
|
}[]
|
||||||
|
onSelect(id: string): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Results = ({ accounts, onSelect }: IResults) => (
|
||||||
|
<>
|
||||||
|
{(accounts || []).map((account: any) => (
|
||||||
|
<button
|
||||||
|
key={account.id}
|
||||||
|
type='button'
|
||||||
|
className='px-4 py-2 w-full flex flex-col hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||||
|
onClick={() => onSelect(account.id)}
|
||||||
|
data-testid='account'
|
||||||
|
>
|
||||||
|
<HStack alignItems='center' space={2}>
|
||||||
|
<Avatar src={account.avatar} size={40} />
|
||||||
|
|
||||||
|
<Stack alignItems='start'>
|
||||||
|
<div className='flex items-center space-x-1 flex-grow'>
|
||||||
|
<Text weight='bold' size='sm' truncate>{account.display_name}</Text>
|
||||||
|
{account.verified && <VerificationBadge />}
|
||||||
|
</div>
|
||||||
|
<Text size='sm' weight='medium' theme='muted' truncate>@{account.acct}</Text>
|
||||||
|
</Stack>
|
||||||
|
</HStack>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Results;
|
Loading…
Reference in New Issue