Add tabs for conversation invitations

Change-Id: I657630c84b4e5f315ccc1b062f28087cd994d2cd
diff --git a/client/src/components/ContactSearchResultList.tsx b/client/src/components/ContactSearchResultList.tsx
index fbe988d..d0593ae 100644
--- a/client/src/components/ContactSearchResultList.tsx
+++ b/client/src/components/ContactSearchResultList.tsx
@@ -16,26 +16,58 @@
  * <https://www.gnu.org/licenses/>.
  */
 import { GroupAddRounded } from '@mui/icons-material';
-import { Box, Dialog, DialogProps, Fab, List, Typography } from '@mui/material';
+import { Box, Dialog, DialogProps, Fab, List, ListSubheader, Typography } from '@mui/material';
 import { useTranslation } from 'react-i18next';
 import { useNavigate } from 'react-router-dom';
 
 import { Contact } from '../models/contact';
-import { useAddContactMutation } from '../services/contactQueries';
+import { useAddContactMutation, useContactsSearchQuery } from '../services/contactQueries';
+import { useConversationsSummariesQuery } from '../services/conversationQueries';
 import ConversationAvatar from './ConversationAvatar';
+import ConversationListItem from './ConversationListItem';
 import { CustomListItemButton } from './CustomListItemButton';
 import { useDialogHandler } from './Dialog';
+import LoadingPage from './Loading';
 
 type ContactSearchResultListProps = {
-  contacts: Contact[];
+  searchFilter: string;
 };
 
-export default ({ contacts }: ContactSearchResultListProps) => {
+export default ({ searchFilter }: ContactSearchResultListProps) => {
+  const { t } = useTranslation();
+
+  const contactsSearchQuery = useContactsSearchQuery(searchFilter);
+  // TODO: Filter conversations
+  const conversationsSummariesQuery = useConversationsSummariesQuery();
+
+  const isLoading = contactsSearchQuery.isLoading && conversationsSummariesQuery.isLoading;
+
+  if (isLoading) {
+    return <LoadingPage />;
+  }
+
+  const conversationsSummaries = conversationsSummariesQuery.data;
+  const contactsSearchResult = contactsSearchQuery.data;
+
   return (
     <List>
-      {contacts?.map((contact) => (
-        <ContactSearchResultListItem key={contact.uri} contact={contact} />
-      ))}
+      {contactsSearchResult && contactsSearchResult.length > 0 && (
+        <>
+          <ListSubheader>{t('search_results')}</ListSubheader>
+          {contactsSearchResult?.map((contact) => (
+            <ContactSearchResultListItem key={contact.uri} contact={contact} />
+          ))}
+        </>
+      )}
+
+      {conversationsSummaries && conversationsSummaries.length > 0 && (
+        <>
+          <ListSubheader>{t('conversations')}</ListSubheader>
+          {conversationsSummaries.map((conversationSummary) => (
+            <ConversationListItem key={conversationSummary.id} conversationSummary={conversationSummary} />
+          ))}
+        </>
+      )}
     </List>
   );
 };
diff --git a/client/src/components/ConversationList.tsx b/client/src/components/ConversationList.tsx
index 3320110..2792fd4 100644
--- a/client/src/components/ConversationList.tsx
+++ b/client/src/components/ConversationList.tsx
@@ -15,42 +15,26 @@
  * License along with this program.  If not, see
  * <https://www.gnu.org/licenses/>.
  */
-import { GroupRounded as GroupIcon, SearchRounded } from '@mui/icons-material';
-import { InputBase } from '@mui/material';
-import List from '@mui/material/List';
-import ListSubheader from '@mui/material/ListSubheader';
-import Typography from '@mui/material/Typography';
-import { Stack } from '@mui/system';
+import { SearchRounded } from '@mui/icons-material';
+import { InputBase, List, Stack } from '@mui/material';
 import { ChangeEvent, useCallback, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 
-import { useContactsSearchQuery } from '../services/contactQueries';
 import { useConversationsSummariesQuery } from '../services/conversationQueries';
 import { SquareButton } from './Button';
 import ContactSearchResultList from './ContactSearchResultList';
 import ConversationListItem from './ConversationListItem';
-import LoadingPage from './Loading';
 import { PeopleGroupIcon } from './SvgIcon';
+import { Tab, TabPanel, Tabs, TabsList } from './Tabs';
 
 export default function ConversationList() {
   const { t } = useTranslation();
-
   const [searchFilter, setSearchFilter] = useState('');
 
-  const conversationsSummariesQuery = useConversationsSummariesQuery();
-  const contactsSearchQuery = useContactsSearchQuery(searchFilter);
-
-  const conversationsSummaries = conversationsSummariesQuery.data;
-  const contactsSearchResult = contactsSearchQuery.data;
-
   const handleInputChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
     setSearchFilter(event.target.value);
   }, []);
 
-  if (!conversationsSummaries) {
-    return <LoadingPage />;
-  }
-
   return (
     <Stack>
       <Stack direction="row">
@@ -65,24 +49,46 @@
         />
         <SquareButton aria-label="start swarm" Icon={PeopleGroupIcon} />
       </Stack>
-      <List>
-        {contactsSearchResult && contactsSearchResult.length > 0 && (
-          <>
-            <ListSubheader>{t('search_results')}</ListSubheader>
-            <ContactSearchResultList contacts={contactsSearchResult} />
-            <ListSubheader>{t('conversations')}</ListSubheader>
-          </>
-        )}
-        {conversationsSummaries.map((conversationSummary) => (
-          <ConversationListItem key={conversationSummary.id} conversationSummary={conversationSummary} />
-        ))}
-        {conversationsSummaries.length === 0 && (
-          <Stack>
-            <GroupIcon color="disabled" fontSize="large" />
-            <Typography variant="subtitle2">{t('no_conversations')}</Typography>
-          </Stack>
-        )}
-      </List>
+      {searchFilter ? <ContactSearchResultList searchFilter={searchFilter} /> : <ConversationTabs />}
     </Stack>
   );
 }
+
+const ConversationTabs = () => {
+  const { t } = useTranslation();
+  const invitations = [];
+
+  return (
+    <Tabs defaultValue={0}>
+      {invitations.length !== 0 && (
+        <TabsList>
+          <Tab>{t('conversations')}</Tab>
+          <Tab>{t('invitations')}</Tab>
+        </TabsList>
+      )}
+      <ConversationsTabPanel />
+      <InvitationsTabPanel />
+    </Tabs>
+  );
+};
+
+const ConversationsTabPanel = () => {
+  const conversationsSummariesQuery = useConversationsSummariesQuery();
+  const conversationsSummaries = conversationsSummariesQuery.data;
+
+  return (
+    <TabPanel value={0}>
+      <List>
+        {conversationsSummaries?.map((conversationSummary) => (
+          <ConversationListItem key={conversationSummary.id} conversationSummary={conversationSummary} />
+        ))}
+      </List>
+    </TabPanel>
+  );
+};
+
+const InvitationsTabPanel = () => {
+  const { t } = useTranslation();
+
+  return <TabPanel value={1}>{t('invitations')}</TabPanel>;
+};
diff --git a/client/src/components/Tabs.tsx b/client/src/components/Tabs.tsx
new file mode 100644
index 0000000..9f7fa20
--- /dev/null
+++ b/client/src/components/Tabs.tsx
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { TabPanelUnstyled, TabsListUnstyled, TabsUnstyled, TabUnstyled, TabUnstyledProps } from '@mui/base';
+import { Theme } from '@mui/material/styles';
+import { styled } from '@mui/system';
+
+type TabProps = TabUnstyledProps & {
+  theme?: Theme;
+};
+
+export const Tab = styled(TabUnstyled)<TabProps>(({ theme }) => ({
+  fontFamily: theme.typography.fontFamily,
+  ...theme.typography.body1,
+  color: '#666666',
+  cursor: 'pointer',
+  border: 'none',
+  display: 'flex',
+  justifyContent: 'center',
+  borderBottom: '4px solid white',
+  backgroundColor: 'white',
+  padding: '9px 0',
+  '&:hover': {
+    color: 'black',
+    borderColor: theme.palette.primary.light,
+    // Attempt to change boldess while keeping same container size
+    // https://stackoverflow.com/a/14978871
+    textShadow: '0 0 .01px black',
+  },
+  '&.Mui-selected': {
+    color: theme.palette.primary.dark,
+    borderColor: theme.palette.primary.dark,
+    // https://stackoverflow.com/a/14978871
+    textShadow: `0 0 .01px ${theme.palette.primary.dark}`,
+  },
+  '&:before': {
+    display: 'block',
+    content: 'attr(content)',
+    fontWeight: 'bold',
+    height: '15px',
+    color: 'transparent',
+    overflow: 'hidden',
+    visibility: 'hidden',
+  },
+}));
+
+export const TabPanel = styled(TabPanelUnstyled)(() => ({
+  width: '100%',
+}));
+
+export const TabsList = styled(TabsListUnstyled)(() => ({
+  display: 'flex',
+  alignItems: 'center',
+  placeContent: 'space-evenly',
+  borderBottom: '1px solid #bfbfbf',
+}));
+
+export const Tabs = TabsUnstyled;