Add conversation requests list

- Add routes to REST API for conversation requests
- Add websocket notification on new conversation requests. This is unreliable.
- Rename 'ColoredCallButton' as 'ColoredRoundButton' and move it to Buttons file for reuse
- Review logic to show conversation tabs
- Add useConversationDisplayNameShort for conversations' names in lists. Will need more work.
- Add hooks to help managing React Query's cache
- Use React Query to remove conversations and update the cache doing so.
- Add ContactService and ConversationService as a way to group reusable functions for the server. This is inspired by jami-android

Known bug: The server often freezes on getContactFromUri (in ContactService) when a new conversation request is received.

Change-Id: I46a60a401f09c3941c864afcdb2625b5fcfe054a
diff --git a/client/src/components/ConversationListItem.tsx b/client/src/components/ConversationListItem.tsx
index 02e856d..e3803a2 100644
--- a/client/src/components/ConversationListItem.tsx
+++ b/client/src/components/ConversationListItem.tsx
@@ -26,10 +26,10 @@
 import { useAuthContext } from '../contexts/AuthProvider';
 import { CallManagerContext } from '../contexts/CallManagerProvider';
 import { CallStatus, useCallContext } from '../contexts/CallProvider';
+import { useConversationDisplayNameShort } from '../hooks/useConversationDisplayName';
 import { useUrlParams } from '../hooks/useUrlParams';
-import { setRefreshFromSlice } from '../redux/appSlice';
-import { useAppDispatch } from '../redux/hooks';
 import { ConversationRouteParams } from '../router';
+import { useRemoveConversationMutation } from '../services/conversationQueries';
 import { getMessageCallText, getMessageMemberText } from '../utils/chatmessages';
 import { formatRelativeDate, formatTime } from '../utils/dates&times';
 import ContextMenu, { ContextMenuHandler, useContextMenuHandler } from './ContextMenu';
@@ -60,9 +60,10 @@
     }
   }, [navigate, conversationId]);
 
-  const conversationName = useMemo(
-    () => conversationSummary.title || conversationSummary.membersNames.join(', ') || account.getDisplayName(),
-    [account, conversationSummary]
+  const conversationName = useConversationDisplayNameShort(
+    account,
+    conversationSummary.title,
+    conversationSummary.membersNames
   );
 
   return (
@@ -78,7 +79,7 @@
         selected={isSelected}
         onClick={onClick}
         onContextMenu={contextMenuHandler.handleAnchorPosition}
-        icon={<ConversationAvatar displayName={conversationName} />}
+        icon={<ConversationAvatar displayName={conversationName} src={conversationSummary.avatar} />}
         primaryText={<Typography variant="body1">{conversationName}</Typography>}
         secondaryText={<SecondaryText conversationSummary={conversationSummary} isSelected={isSelected} />}
       />
@@ -260,7 +261,11 @@
         isSwarm={isSwarm}
       />
 
-      <RemoveConversationDialog {...RemoveConversationDialogHandler.props} conversationId={conversationId} />
+      <RemoveConversationDialog
+        {...RemoveConversationDialogHandler.props}
+        conversationId={conversationId}
+        isSelected={isSelected}
+      />
     </>
   );
 };
@@ -309,28 +314,34 @@
 
 interface RemoveConversationDialogProps {
   conversationId: string;
+  isSelected: boolean;
   open: boolean;
   onClose: () => void;
 }
 
-const RemoveConversationDialog = ({ conversationId, open, onClose }: RemoveConversationDialogProps) => {
-  const { axiosInstance } = useAuthContext();
+const RemoveConversationDialog = ({ conversationId, isSelected, open, onClose }: RemoveConversationDialogProps) => {
   const { t } = useTranslation();
-  const dispatch = useAppDispatch();
+  const navigate = useNavigate();
+  const removeConversationMutation = useRemoveConversationMutation();
 
-  const remove = async () => {
-    const controller = new AbortController();
-    try {
-      await axiosInstance.delete(`/conversations/${conversationId}`, {
-        signal: controller.signal,
-      });
-      dispatch(setRefreshFromSlice());
-    } catch (e) {
-      console.error(`Error removing conversation : `, e);
-      dispatch(setRefreshFromSlice());
-    }
-    onClose();
-  };
+  const remove = useCallback(async () => {
+    removeConversationMutation.mutate(
+      { conversationId },
+      {
+        onSuccess: () => {
+          if (isSelected) {
+            navigate('/conversation/');
+          }
+        },
+        onError: (e) => {
+          console.error(`Error removing conversation : `, e);
+        },
+        onSettled: () => {
+          onClose();
+        },
+      }
+    );
+  }, [conversationId, isSelected, navigate, onClose, removeConversationMutation]);
 
   return (
     <ConfirmationDialog