Uniformize styles for dialogs and popovers, add ContextMenu

Change-Id: I8687b2d171f9c15e8eb8dd5ba2a32cdaa27b70d6
diff --git a/client/src/components/ContextMenu.tsx b/client/src/components/ContextMenu.tsx
new file mode 100644
index 0000000..6ac1363
--- /dev/null
+++ b/client/src/components/ContextMenu.tsx
@@ -0,0 +1,80 @@
+/*
+ * 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 { Menu, MenuProps, PopoverPosition, PopoverReference, styled } from '@mui/material';
+import { MouseEventHandler, useCallback, useMemo, useState } from 'react';
+
+import PopoverList, { PopoverListItemData } from './PopoverList';
+
+export interface ContextMenuHandler {
+  props: {
+    open: boolean;
+    onClose: () => void;
+    anchorPosition: PopoverPosition | undefined;
+    anchorReference: PopoverReference | undefined;
+  };
+  handleAnchorPosition: MouseEventHandler;
+}
+
+export const useContextMenuHandler = (): ContextMenuHandler => {
+  const [anchorPosition, setAnchorPosition] = useState<PopoverPosition | undefined>(undefined);
+
+  const handleAnchorPosition = useCallback<MouseEventHandler>(
+    (event) => {
+      event.preventDefault();
+      setAnchorPosition((anchorPosition) =>
+        anchorPosition === undefined ? { top: event.clientY, left: event.clientX } : undefined
+      );
+    },
+    [setAnchorPosition]
+  );
+
+  const onClose = useCallback(() => setAnchorPosition(undefined), [setAnchorPosition]);
+
+  return useMemo(
+    () => ({
+      props: {
+        open: !!anchorPosition,
+        onClose,
+        anchorPosition,
+        anchorReference: 'anchorPosition',
+      },
+      handleAnchorPosition,
+    }),
+    [anchorPosition, handleAnchorPosition, onClose]
+  );
+};
+
+interface ContextMenuProps extends MenuProps {
+  items: PopoverListItemData[];
+}
+
+const ContextMenu = styled(({ items, ...props }: ContextMenuProps) => (
+  <Menu {...props}>
+    <PopoverList items={items} onClose={props.onClose} />
+  </Menu>
+))(() => ({
+  '& .MuiPaper-root': {
+    borderRadius: '5px 20px 20px 20px',
+    boxShadow: '3px 3px 7px #00000029',
+  },
+  '& .MuiMenu-list': {
+    padding: '0px',
+  },
+}));
+
+export default ContextMenu;
diff --git a/client/src/components/ConversationListItem.tsx b/client/src/components/ConversationListItem.tsx
index 2f07076..2861eb8 100644
--- a/client/src/components/ConversationListItem.tsx
+++ b/client/src/components/ConversationListItem.tsx
@@ -15,28 +15,20 @@
  * License along with this program.  If not, see
  * <https://www.gnu.org/licenses/>.
  */
-import {
-  Box,
-  ListItem,
-  ListItemAvatar,
-  ListItemIcon,
-  ListItemText,
-  Menu,
-  MenuItem,
-  Stack,
-  Typography,
-} from '@mui/material';
+import { Box, ListItem, ListItemAvatar, ListItemText } from '@mui/material';
 import { Conversation } from 'jami-web-common';
 import { QRCodeCanvas } from 'qrcode.react';
-import { MouseEvent, useState } from 'react';
+import { useCallback, useMemo, useState } from 'react';
 import { useTranslation } from 'react-i18next';
-import Modal from 'react-modal';
 import { useNavigate, useParams } from 'react-router-dom';
 
 import { useAuthContext } from '../contexts/AuthProvider';
 import { setRefreshFromSlice } from '../redux/appSlice';
 import { useAppDispatch } from '../redux/hooks';
+import ContextMenu, { ContextMenuHandler, useContextMenuHandler } from './ContextMenu';
 import ConversationAvatar from './ConversationAvatar';
+import { ConfirmationDialog, DialogContentList, InfosDialog, useDialogHandler } from './Dialog';
+import { PopoverListItemData } from './PopoverList';
 import {
   AudioCallIcon,
   BlockContactIcon,
@@ -47,75 +39,58 @@
   VideoCallIcon,
 } from './SvgIcon';
 
-const cancelStyles: Modal.Styles = {
-  content: {
-    left: '94px',
-    width: '300px',
-    height: '220px',
-    background: '#FFFFFF 0% 0% no-repeat padding-box',
-    boxShadow: '3px 3px 7px #00000029',
-    borderRadius: '20px',
-    opacity: '1',
-
-    textAlign: 'left',
-    font: 'normal normal normal 12px/26px Ubuntu',
-    letterSpacing: '0px',
-    color: '#000000',
-  },
-};
-
-const contactDetailsStyles: Modal.Styles = {
-  content: {
-    left: '94px',
-    width: '450px',
-    height: '450px',
-    background: '#FFFFFF 0% 0% no-repeat padding-box',
-    boxShadow: '3px 3px 7px #00000029',
-    borderRadius: '20px',
-    opacity: '1',
-
-    textAlign: 'left',
-    font: 'normal normal normal 12px/26px Ubuntu',
-    letterSpacing: '0px',
-    color: '#000000',
-  },
-};
-
-const iconColor = '#005699';
-
 type ConversationListItemProps = {
   conversation: Conversation;
 };
 
 export default function ConversationListItem({ conversation }: ConversationListItemProps) {
-  const { axiosInstance } = useAuthContext();
   const { conversationId, contactId } = useParams();
-  const dispatch = useAppDispatch();
+  const contextMenuHandler = useContextMenuHandler();
 
   const pathId = conversationId || contactId;
   const isSelected = conversation.getDisplayUri() === pathId;
   const navigate = useNavigate();
-  const { t } = useTranslation();
-
-  const [menuAnchorEl, setMenuAnchorEl] = useState<HTMLElement | null>(null);
-  const [modalDetailsIsOpen, setModalDetailsIsOpen] = useState(false);
-  const [modalDeleteIsOpen, setModalDeleteIsOpen] = useState(false);
-  const [blockOrRemove, setBlockOrRemove] = useState(true);
   const [userId] = useState(conversation?.getFirstMember()?.contact.getUri());
+  const uri = conversation.getId() ? `/conversation/${conversation.getId()}` : `/add-contact/${userId}`;
+  return (
+    <Box onContextMenu={contextMenuHandler.handleAnchorPosition}>
+      <ConversationMenu
+        userId={userId}
+        conversation={conversation}
+        uri={uri}
+        isSelected={isSelected}
+        contextMenuProps={contextMenuHandler.props}
+      />
+      <ListItem button alignItems="flex-start" selected={isSelected} onClick={() => navigate(uri)}>
+        <ListItemAvatar>
+          <ConversationAvatar displayName={conversation.getDisplayNameNoFallback()} />
+        </ListItemAvatar>
+        <ListItemText primary={conversation.getDisplayName()} secondary={conversation.getDisplayUri()} />
+      </ListItem>
+    </Box>
+  );
+}
+
+interface ConversationMenuProps {
+  userId: string;
+  conversation: Conversation;
+  uri: string;
+  isSelected: boolean;
+  contextMenuProps: ContextMenuHandler['props'];
+}
+
+const ConversationMenu = ({ userId, conversation, uri, isSelected, contextMenuProps }: ConversationMenuProps) => {
+  const { t } = useTranslation();
+  const { axiosInstance } = useAuthContext();
   const [isSwarm] = useState(true);
 
-  const openMenu = (e: MouseEvent<HTMLDivElement>) => {
-    e.preventDefault();
-    console.log(e);
-    setMenuAnchorEl(e.currentTarget);
-  };
-  const openModalDetails = () => setModalDetailsIsOpen(true);
-  const openModalDelete = () => setModalDeleteIsOpen(true);
-  const closeModal = () => setMenuAnchorEl(null);
-  const closeModalDetails = () => setModalDetailsIsOpen(false);
-  const closeModalDelete = () => setModalDeleteIsOpen(false);
+  const detailsDialogHandler = useDialogHandler();
+  const blockContactDialogHandler = useDialogHandler();
+  const removeContactDialogHandler = useDialogHandler();
 
-  const getContactDetails = async () => {
+  const navigate = useNavigate();
+
+  const getContactDetails = useCallback(async () => {
     const controller = new AbortController();
     try {
       const { data } = await axiosInstance.get(`/contacts/${userId}`, {
@@ -125,274 +100,210 @@
     } catch (e) {
       console.log('ERROR GET CONTACT DETAILS: ', e);
     }
-  };
+  }, [axiosInstance, userId]);
 
-  const removeOrBlock = async (block = false) => {
-    setBlockOrRemove(false);
+  const menuOptions: PopoverListItemData[] = useMemo(
+    () => [
+      {
+        label: t('conversation_message'),
+        Icon: MessageIcon,
+        onClick: () => {
+          navigate(uri);
+        },
+      },
+      {
+        label: t('conversation_start_audiocall'),
+        Icon: AudioCallIcon,
+        onClick: () => {
+          navigate(`/account/call/${conversation.getId()}`);
+        },
+      },
+      {
+        label: t('conversation_start_videocall'),
+        Icon: VideoCallIcon,
+        onClick: () => {
+          navigate(`call/${conversation.getId()}?video=true`);
+        },
+      },
+      ...(isSelected
+        ? [
+            {
+              label: t('conversation_close'),
+              Icon: CancelIcon,
+              onClick: () => {
+                navigate(`/`);
+              },
+            },
+          ]
+        : []),
+      {
+        label: t('conversation_details'),
+        Icon: ContactDetailsIcon,
+        onClick: () => {
+          detailsDialogHandler.openDialog();
+          getContactDetails();
+        },
+      },
+      {
+        label: t('conversation_block'),
+        Icon: BlockContactIcon,
+        onClick: () => {
+          blockContactDialogHandler.openDialog();
+        },
+      },
+      {
+        label: t('conversation_delete'),
+        Icon: RemoveContactIcon,
+        onClick: () => {
+          removeContactDialogHandler.openDialog();
+        },
+      },
+    ],
+    [
+      conversation,
+      navigate,
+      uri,
+      isSelected,
+      getContactDetails,
+      detailsDialogHandler,
+      blockContactDialogHandler,
+      removeContactDialogHandler,
+      t,
+    ]
+  );
 
+  return (
+    <>
+      <ContextMenu {...contextMenuProps} items={menuOptions} />
+
+      <DetailsDialog {...detailsDialogHandler.props} userId={userId} conversation={conversation} isSwarm={isSwarm} />
+
+      <RemoveContactDialog {...removeContactDialogHandler.props} userId={userId} conversation={conversation} />
+
+      <BlockContactDialog {...blockContactDialogHandler.props} userId={userId} conversation={conversation} />
+    </>
+  );
+};
+
+interface DetailsDialogProps {
+  userId: string;
+  conversation: Conversation;
+  open: boolean;
+  onClose: () => void;
+  isSwarm: boolean;
+}
+
+const DetailsDialog = ({ userId, conversation, open, onClose, isSwarm }: DetailsDialogProps) => {
+  const { t } = useTranslation();
+  const items = useMemo(
+    () => [
+      {
+        label: t('conversation_details_username'),
+        value: conversation.getDisplayNameNoFallback(),
+      },
+      {
+        label: t('conversation_details_identifier'),
+        value: userId,
+      },
+      {
+        label: t('conversation_details_qr_code'),
+        value: <QRCodeCanvas size={80} value={`${userId}`} />,
+      },
+      {
+        label: t('conversation_details_is_swarm'),
+        value: isSwarm ? t('conversation_details_is_swarm_true') : t('conversation_details_is_swarm_false'),
+      },
+    ],
+    [userId, conversation, isSwarm, t]
+  );
+  return (
+    <InfosDialog
+      open={open}
+      onClose={onClose}
+      icon={
+        <ConversationAvatar
+          sx={{ width: 'inherit', height: 'inherit' }}
+          displayName={conversation.getDisplayNameNoFallback()}
+        />
+      }
+      title={conversation.getDisplayNameNoFallback() || ''}
+      content={<DialogContentList title={t('conversation_details_informations')} items={items} />}
+    />
+  );
+};
+
+interface BlockContactDialogProps {
+  userId: string;
+  conversation: Conversation;
+  open: boolean;
+  onClose: () => void;
+}
+
+const BlockContactDialog = ({ userId, open, onClose }: BlockContactDialogProps) => {
+  const { axiosInstance } = useAuthContext();
+  const { t } = useTranslation();
+  const dispatch = useAppDispatch();
+
+  const block = async () => {
     const controller = new AbortController();
-    let url = `/contacts/${userId}`;
-    if (block) {
-      url += '/block';
-    }
     try {
-      await axiosInstance(url, {
+      await axiosInstance.post(`/contacts/${userId}/block`, {
         signal: controller.signal,
-        method: block ? 'POST' : 'DELETE',
       });
       dispatch(setRefreshFromSlice());
     } catch (e) {
-      console.error(`Error ${block ? 'blocking' : 'removing'} contact : `, e);
+      console.error(`Error $block contact : `, e);
       dispatch(setRefreshFromSlice());
     }
-    closeModalDelete();
+    onClose();
   };
 
-  const uri = conversation.getId() ? `/conversation/${conversation.getId()}` : `/add-contact/${userId}`;
   return (
-    <div onContextMenu={openMenu}>
-      <div>
-        <Menu open={!!menuAnchorEl} onClose={closeModal} anchorEl={menuAnchorEl}>
-          <MenuItem
-            onClick={() => {
-              navigate(uri);
-              closeModal();
-            }}
-          >
-            <ListItemIcon>
-              <MessageIcon style={{ color: iconColor }} />
-            </ListItemIcon>
-            <ListItemText>{t('conversation_message')}</ListItemText>
-          </MenuItem>
-          <MenuItem
-            onClick={() => {
-              navigate(`/account/call/${conversation.getId()}`);
-            }}
-          >
-            <ListItemIcon>
-              <AudioCallIcon style={{ color: iconColor }} />
-            </ListItemIcon>
-            <ListItemText>{t('conversation_start_audiocall')}</ListItemText>
-          </MenuItem>
-
-          <MenuItem
-            onClick={() => {
-              navigate(`call/${conversation.getId()}?video=true`);
-            }}
-          >
-            <ListItemIcon>
-              <VideoCallIcon style={{ color: iconColor }} />
-            </ListItemIcon>
-            <ListItemText>{t('conversation_start_videocall')}</ListItemText>
-          </MenuItem>
-
-          {isSelected && (
-            <MenuItem
-              onClick={() => {
-                navigate(`/`);
-                closeModal();
-              }}
-            >
-              <ListItemIcon>
-                <CancelIcon style={{ color: iconColor }} />
-              </ListItemIcon>
-              <ListItemText>{t('conversation_close')}</ListItemText>
-            </MenuItem>
-          )}
-
-          <MenuItem
-            onClick={() => {
-              console.log('open details contact for: ');
-              closeModal();
-              openModalDetails();
-              getContactDetails();
-            }}
-          >
-            <ListItemIcon>
-              <ContactDetailsIcon style={{ color: iconColor }} />
-            </ListItemIcon>
-            <ListItemText>{t('conversation_details')}</ListItemText>
-          </MenuItem>
-
-          <MenuItem
-            onClick={() => {
-              setBlockOrRemove(true);
-              closeModal();
-              openModalDelete();
-            }}
-          >
-            <ListItemIcon>
-              <BlockContactIcon style={{ color: iconColor }} />
-            </ListItemIcon>
-            <ListItemText>{t('conversation_block_contact')}</ListItemText>
-          </MenuItem>
-
-          <MenuItem
-            onClick={() => {
-              setBlockOrRemove(false);
-              closeModal();
-              openModalDelete();
-            }}
-          >
-            <ListItemIcon>
-              <RemoveContactIcon style={{ color: iconColor }} />
-            </ListItemIcon>
-            <ListItemText>{t('conversation_delete_contact')}</ListItemText>
-          </MenuItem>
-        </Menu>
-      </div>
-
-      <div>
-        <Modal
-          isOpen={modalDetailsIsOpen}
-          onRequestClose={closeModalDetails}
-          style={contactDetailsStyles}
-          contentLabel="Détails contact"
-        >
-          <Stack direction={'row'} alignContent="flex-end">
-            <Stack direction={'column'}>
-              <div style={{ height: '100px' }}>
-                <ConversationAvatar displayName={conversation.getDisplayNameNoFallback()} />
-              </div>
-
-              <div
-                style={{
-                  fontSize: '20px',
-                  marginBottom: '20px',
-                  height: '20px',
-                }}
-              >
-                Informations
-              </div>
-
-              <Typography variant="caption">Nom d&apos;utilisateur</Typography>
-              <div style={{ height: '20px' }} />
-              <Typography variant="caption">Identifiant </Typography>
-              <div style={{ height: '20px' }} />
-
-              <div
-                style={{
-                  flex: 1,
-                  height: '150px',
-                  flexDirection: 'column',
-                  // alignSelf: "flex-end",
-                }}
-              >
-                <Typography variant="caption">Code QR</Typography>
-              </div>
-
-              <Typography variant="caption">est un swarm </Typography>
-            </Stack>
-
-            <Stack direction={'column'}>
-              <div
-                style={{
-                  fontWeight: 'bold',
-                  fontSize: '20px',
-                  height: '100px',
-                }}
-              >
-                {conversation.getDisplayNameNoFallback() + '(resolved name)'}
-              </div>
-
-              <div
-                style={{
-                  height: '40px',
-                }}
-              />
-              <Typography variant="caption">
-                <div style={{ fontWeight: 'bold' }}>{conversation.getDisplayNameNoFallback()}</div>
-              </Typography>
-
-              <div style={{ height: '20px' }} />
-
-              <Typography variant="caption">
-                <div style={{ fontWeight: 'bold' }}> {userId}</div>
-              </Typography>
-
-              <div style={{ height: '20px' }} />
-
-              <div>
-                <QRCodeCanvas size={40} value={`${userId}`} />
-              </div>
-
-              <Typography variant="caption">
-                <div style={{ fontWeight: 'bold' }}> {String(isSwarm)}</div>
-              </Typography>
-            </Stack>
-          </Stack>
-          <div
-            onClick={closeModalDetails}
-            style={{
-              width: '100px',
-              borderStyle: 'solid',
-              textAlign: 'center',
-              borderRadius: '5px',
-              marginLeft: '150px',
-              marginTop: '10px',
-            }}
-          >
-            <Typography variant="caption">Fermer</Typography>
-          </div>
-        </Modal>
-      </div>
-
-      <div>
-        <Modal
-          isOpen={modalDeleteIsOpen}
-          onRequestClose={closeModalDelete}
-          style={cancelStyles}
-          contentLabel="Merci de confirmer"
-        >
-          <Typography variant="h4">Merci de confirmer</Typography>
-          <Stack direction={'column'} justifyContent="space-around" spacing={'75px'}>
-            <div style={{ textAlign: 'center', marginTop: '10%' }}>
-              <Typography variant="body2">
-                Voulez vous vraiment {blockOrRemove ? 'bloquer' : 'supprimer'} ce contact?
-              </Typography>
-            </div>
-
-            <Stack direction={'row'} top={'25px'} alignSelf="center" spacing={1}>
-              <Box
-                onClick={() => {
-                  if (blockOrRemove) removeOrBlock(true);
-                  else removeOrBlock(false);
-                }}
-                style={{
-                  width: '100px',
-                  textAlign: 'center',
-                  borderStyle: 'solid',
-                  borderColor: 'red',
-                  borderRadius: '10px',
-                  color: 'red',
-                }}
-              >
-                {blockOrRemove ? 'Bloquer' : 'Supprimer'}
-              </Box>
-              <Box
-                onClick={closeModalDelete}
-                style={{
-                  width: '100px',
-                  textAlign: 'center',
-                  paddingLeft: '12px',
-                  paddingRight: '12px',
-                  borderStyle: 'solid',
-                  borderRadius: '10px',
-                }}
-              >
-                Annuler
-              </Box>
-            </Stack>
-          </Stack>
-        </Modal>
-      </div>
-
-      <ListItem button alignItems="flex-start" selected={isSelected} onClick={() => navigate(uri)}>
-        <ListItemAvatar>
-          <ConversationAvatar displayName={conversation.getDisplayNameNoFallback()} />
-        </ListItemAvatar>
-        <ListItemText primary={conversation.getDisplayName()} secondary={conversation.getDisplayUri()} />
-      </ListItem>
-    </div>
+    <ConfirmationDialog
+      open={open}
+      onClose={onClose}
+      title={t('dialog_confirm_title_default')}
+      content={t('conversation_ask_confirm_block')}
+      onConfirm={block}
+      confirmButtonText={t('conversation_confirm_block')}
+    />
   );
+};
+
+interface RemoveContactDialogProps {
+  userId: string;
+  conversation: Conversation;
+  open: boolean;
+  onClose: () => void;
 }
+
+const RemoveContactDialog = ({ userId, open, onClose }: RemoveContactDialogProps) => {
+  const { axiosInstance } = useAuthContext();
+  const { t } = useTranslation();
+  const dispatch = useAppDispatch();
+
+  const remove = async () => {
+    const controller = new AbortController();
+    try {
+      await axiosInstance.delete(`/contacts/${userId}/remove`, {
+        signal: controller.signal,
+      });
+      dispatch(setRefreshFromSlice());
+    } catch (e) {
+      console.error(`Error removing contact : `, e);
+      dispatch(setRefreshFromSlice());
+    }
+    onClose();
+  };
+
+  return (
+    <ConfirmationDialog
+      open={open}
+      onClose={onClose}
+      title={t('dialog_confirm_title_default')}
+      content={t('conversation_ask_confirm_remove')}
+      onConfirm={remove}
+      confirmButtonText={t('conversation_confirm_remove')}
+    />
+  );
+};
diff --git a/client/src/components/Dialog.tsx b/client/src/components/Dialog.tsx
new file mode 100644
index 0000000..9a6790d
--- /dev/null
+++ b/client/src/components/Dialog.tsx
@@ -0,0 +1,161 @@
+/*
+ * 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 { Box, Button, List, ListItem, ListItemIcon, Stack, SvgIconProps, Typography } from '@mui/material';
+import Dialog from '@mui/material/Dialog';
+import DialogActions from '@mui/material/DialogActions';
+import DialogContent from '@mui/material/DialogContent';
+import DialogTitle from '@mui/material/DialogTitle';
+import { ComponentType, ReactNode, useCallback, useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+
+interface DialogHandler {
+  props: {
+    open: boolean;
+    onClose: () => void;
+  };
+  openDialog: () => void;
+}
+
+export const useDialogHandler = (): DialogHandler => {
+  const [open, setOpen] = useState(false);
+
+  const onClose = useCallback(() => {
+    setOpen(false);
+  }, []);
+
+  const openDialog = useCallback(() => {
+    setOpen(true);
+  }, []);
+
+  return useMemo(
+    () => ({
+      props: { open, onClose },
+      openDialog,
+    }),
+    [open, onClose, openDialog]
+  );
+};
+
+interface BaseDialogProps {
+  open: boolean;
+  onClose: () => void;
+  icon?: ReactNode;
+  title: string;
+  content: ReactNode;
+  actions: ReactNode;
+}
+
+export const BaseDialog = ({ open, onClose, icon, title, content, actions }: BaseDialogProps) => {
+  return (
+    <Dialog open={open} onClose={onClose}>
+      <DialogTitle>
+        <Stack direction="row" alignItems="center" spacing="16px">
+          {icon && (
+            <Box height="80px" width="80px">
+              {icon}
+            </Box>
+          )}
+          <Box>
+            <Typography variant="h2">{title}</Typography>
+          </Box>
+        </Stack>
+      </DialogTitle>
+      <DialogContent>{content}</DialogContent>
+      <DialogActions>{actions}</DialogActions>
+    </Dialog>
+  );
+};
+
+type InfosDialogProps = Omit<BaseDialogProps, 'actions'>;
+
+export const InfosDialog = (props: InfosDialogProps) => {
+  const { t } = useTranslation();
+  return (
+    <BaseDialog
+      {...props}
+      actions={
+        <Button onClick={props.onClose} autoFocus>
+          {t('dialog_close')}
+        </Button>
+      }
+    />
+  );
+};
+
+interface ConfirmationDialogProps extends Omit<BaseDialogProps, 'actions'> {
+  onConfirm: () => void;
+  confirmButtonText: string;
+}
+
+export const ConfirmationDialog = ({ onConfirm, confirmButtonText, ...props }: ConfirmationDialogProps) => {
+  const { t } = useTranslation();
+  props.title = props.title || t('dialog_confirm_title_default');
+
+  return (
+    <BaseDialog
+      {...props}
+      actions={
+        <>
+          <Button onClick={onConfirm}>{confirmButtonText}</Button>
+          <Button onClick={props.onClose}>{t('dialog_cancel')}</Button>
+        </>
+      }
+    />
+  );
+};
+
+interface DialogContentListItem {
+  Icon?: ComponentType<SvgIconProps>;
+  label?: string;
+  value: ReactNode;
+}
+
+interface DialogContentListProps {
+  title?: string;
+  items: DialogContentListItem[];
+}
+
+export const DialogContentList = ({ title, items }: DialogContentListProps) => {
+  return (
+    <List subheader={<Typography variant="h3">{title}</Typography>}>
+      {items.map(({ Icon, label, value }, index) => (
+        <ListItem key={index}>
+          {Icon && (
+            <ListItemIcon>
+              <Icon />
+            </ListItemIcon>
+          )}
+          <Stack direction="row" alignItems="center" spacing="24px">
+            {label && (
+              <Stack direction="row" width="100px" justifyContent="end">
+                <Typography variant="body2" color="#a0a0a0">
+                  {label}
+                </Typography>
+              </Stack>
+            )}
+            <Box>
+              <Typography variant="body2" sx={{ fontWeight: 'bold' }}>
+                {value}
+              </Typography>
+            </Box>
+          </Stack>
+        </ListItem>
+      ))}
+    </List>
+  );
+};
diff --git a/client/src/components/Input.tsx b/client/src/components/Input.tsx
index b21b2d2..ec840c4 100644
--- a/client/src/components/Input.tsx
+++ b/client/src/components/Input.tsx
@@ -16,23 +16,14 @@
  * <https://www.gnu.org/licenses/>.
  */
 import { GppMaybe, Warning } from '@mui/icons-material';
-import {
-  IconButtonProps,
-  List,
-  ListItem,
-  ListItemIcon,
-  Stack,
-  TextField,
-  TextFieldProps,
-  Typography,
-} from '@mui/material';
+import { IconButtonProps, Stack, TextField, TextFieldProps } from '@mui/material';
 import { styled } from '@mui/material/styles';
-import { ChangeEvent, ReactElement, useCallback, useEffect, useState } from 'react';
+import { ChangeEvent, ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 
 import { StrengthValueCode } from '../utils/auth';
 import { InfoButton, ToggleVisibilityButton } from './Button';
-import RulesDialog from './RulesDialog';
+import { DialogContentList, InfosDialog, useDialogHandler } from './Dialog';
 import { CheckedIcon, LockIcon, PenIcon, PersonIcon, RoundSaltireIcon } from './SvgIcon';
 
 const iconsHeight = '16px';
@@ -71,7 +62,7 @@
   const [isSelected, setIsSelected] = useState(false);
   const [input, setInput] = useState(props.defaultValue);
   const [startAdornment, setStartAdornment] = useState<ReactElement | undefined>();
-  const [isDialogOpened, setIsDialogOpened] = useState<boolean>(false);
+  const dialogHandler = useDialogHandler();
 
   const onChange = useCallback(
     (event: ChangeEvent<HTMLInputElement>) => {
@@ -105,13 +96,7 @@
 
   return (
     <>
-      <RulesDialog
-        openDialog={isDialogOpened}
-        title={t('username_rules_dialog_title')}
-        closeDialog={() => setIsDialogOpened(false)}
-      >
-        <UsernameRules />
-      </RulesDialog>
+      <InfosDialog {...dialogHandler.props} title={t('username_rules_dialog_title')} content={<UsernameRules />} />
       <TextField
         color={inputColor(props.error, success)}
         label={t('username_input_label')}
@@ -128,7 +113,7 @@
         InputProps={{
           startAdornment,
           endAdornment: (
-            <InfoButton tooltipTitle={tooltipTitle} {...infoButtonProps} onClick={() => setIsDialogOpened(true)} />
+            <InfoButton tooltipTitle={tooltipTitle} {...infoButtonProps} onClick={dialogHandler.openDialog} />
           ),
           ...props.InputProps,
         }}
@@ -150,7 +135,7 @@
   const [isSelected, setIsSelected] = useState(false);
   const [input, setInput] = useState(props.defaultValue);
   const [startAdornment, setStartAdornment] = useState<ReactElement | undefined>();
-  const [isDialogOpened, setIsDialogOpened] = useState<boolean>(false);
+  const dialogHandler = useDialogHandler();
 
   const toggleShowPassword = () => {
     setShowPassword((showPassword) => !showPassword);
@@ -189,13 +174,7 @@
 
   return (
     <>
-      <RulesDialog
-        openDialog={isDialogOpened}
-        title={t('password_rules_dialog_title')}
-        closeDialog={() => setIsDialogOpened(false)}
-      >
-        <PasswordRules />
-      </RulesDialog>
+      <InfosDialog {...dialogHandler.props} title={t('password_rules_dialog_title')} content={<PasswordRules />} />
       <TextField
         color={inputColor(props.error, success)}
         label={t('password_input_label')}
@@ -212,7 +191,7 @@
           startAdornment,
           endAdornment: (
             <Stack direction="row" spacing="14px" alignItems="center">
-              <InfoButton tooltipTitle={tooltipTitle} {...infoButtonProps} onClick={() => setIsDialogOpened(true)} />
+              <InfoButton tooltipTitle={tooltipTitle} {...infoButtonProps} onClick={dialogHandler.openDialog} />
               <ToggleVisibilityButton visible={showPassword} onClick={toggleShowPassword} />
             </Stack>
           ),
@@ -300,72 +279,56 @@
 
 const PasswordRules = () => {
   const { t } = useTranslation();
-
-  return (
-    <List>
-      <ListItem>
-        <ListItemIcon>
-          <GppMaybe />
-        </ListItemIcon>
-        <Typography variant="body1">{t('password_rule_one')}</Typography>
-      </ListItem>
-      <ListItem>
-        <ListItemIcon>
-          <GppMaybe />
-        </ListItemIcon>
-        <Typography variant="body1">{t('password_rule_two')}</Typography>
-      </ListItem>
-      <ListItem>
-        <ListItemIcon>
-          <GppMaybe />
-        </ListItemIcon>
-        <Typography variant="body1">{t('password_rule_three')}</Typography>
-      </ListItem>
-      <ListItem>
-        <ListItemIcon>
-          <GppMaybe />
-        </ListItemIcon>
-        <Typography variant="body1">{t('password_rule_four')}</Typography>
-      </ListItem>
-      <ListItem>
-        <ListItemIcon>
-          <GppMaybe />
-        </ListItemIcon>
-        <Typography variant="body1">{t('password_rule_five')}</Typography>
-      </ListItem>
-    </List>
+  const items = useMemo(
+    () => [
+      {
+        Icon: GppMaybe,
+        value: t('password_rule_one'),
+      },
+      {
+        Icon: GppMaybe,
+        value: t('password_rule_two'),
+      },
+      {
+        Icon: GppMaybe,
+        value: t('password_rule_three'),
+      },
+      {
+        Icon: GppMaybe,
+        value: t('password_rule_four'),
+      },
+      {
+        Icon: GppMaybe,
+        value: t('password_rule_five'),
+      },
+    ],
+    [t]
   );
+  return <DialogContentList items={items} />;
 };
 
 const UsernameRules = () => {
   const { t } = useTranslation();
-
-  return (
-    <List>
-      <ListItem>
-        <ListItemIcon>
-          <Warning />
-        </ListItemIcon>
-        <Typography variant="body1">{t('username_rule_one')}</Typography>
-      </ListItem>
-      <ListItem>
-        <ListItemIcon>
-          <Warning />
-        </ListItemIcon>
-        <Typography variant="body1">{t('username_rule_two')}</Typography>
-      </ListItem>
-      <ListItem>
-        <ListItemIcon>
-          <Warning />
-        </ListItemIcon>
-        <Typography variant="body1">{t('username_rule_three')}</Typography>
-      </ListItem>
-      <ListItem>
-        <ListItemIcon>
-          <Warning />
-        </ListItemIcon>
-        <Typography variant="body1">{t('username_rule_four')}</Typography>
-      </ListItem>
-    </List>
+  const items = useMemo(
+    () => [
+      {
+        Icon: Warning,
+        value: t('username_rule_one'),
+      },
+      {
+        Icon: Warning,
+        value: t('username_rule_two'),
+      },
+      {
+        Icon: Warning,
+        value: t('username_rule_three'),
+      },
+      {
+        Icon: Warning,
+        value: t('username_rule_four'),
+      },
+    ],
+    [t]
   );
+  return <DialogContentList items={items} />;
 };
diff --git a/client/src/components/Message.tsx b/client/src/components/Message.tsx
index 6b2fad8..7cf7225 100644
--- a/client/src/components/Message.tsx
+++ b/client/src/components/Message.tsx
@@ -15,18 +15,7 @@
  * License along with this program.  If not, see
  * <https://www.gnu.org/licenses/>.
  */
-import {
-  Box,
-  Chip,
-  Divider,
-  List,
-  ListItemButton,
-  ListItemText,
-  Stack,
-  Theme,
-  Tooltip,
-  Typography,
-} from '@mui/material';
+import { Box, Chip, Divider, Stack, Tooltip, Typography } from '@mui/material';
 import { styled } from '@mui/material/styles';
 import { Dayjs } from 'dayjs';
 import { Account, Contact, Message } from 'jami-web-common';
@@ -36,6 +25,7 @@
 import dayjs from '../dayjsInitializer';
 import { EmojiButton, MoreButton, ReplyMessageButton } from './Button';
 import ConversationAvatar from './ConversationAvatar';
+import PopoverList, { PopoverListItemData } from './PopoverList';
 import {
   ArrowLeftCurved,
   ArrowLeftDown,
@@ -408,21 +398,21 @@
 const MessageTooltip = styled(({ className, position, children }: MessageTooltipProps) => {
   const [open, setOpen] = useState(false);
   const emojis = ['😎', '😄', '😍']; // Should be last three used emojis
-  const additionalOptions = [
+  const additionalOptions: PopoverListItemData[] = [
     {
       Icon: TwoSheetsIcon,
-      text: 'Copy',
-      action: () => {},
+      label: 'Copy',
+      onClick: () => {},
     },
     {
       Icon: OppositeArrowsIcon,
-      text: 'Transfer',
-      action: () => {},
+      label: 'Transfer',
+      onClick: () => {},
     },
     {
       Icon: TrashBinIcon,
-      text: 'Delete message',
-      action: () => {},
+      label: 'Delete message',
+      onClick: () => {},
     },
   ];
 
@@ -449,10 +439,10 @@
       onClose={onClose}
       title={
         <Stack>
-          {/* Whole tooltip's content */}
           <Stack // Main options
             direction="row"
             spacing="16px"
+            padding="16px"
           >
             {emojis.map((emoji) => (
               <EmojiButton key={emoji} emoji={emoji} />
@@ -460,43 +450,10 @@
             <ReplyMessageButton />
             <MoreButton onClick={toggleMoreMenu} />
           </Stack>
-          {open && ( // Additional menu options
+          {open && (
             <>
-              <Divider sx={{ paddingTop: '16px' }} />
-              <List sx={{ padding: 0, paddingTop: '8px', marginBottom: '-8px' }}>
-                {additionalOptions.map((option) => (
-                  <ListItemButton
-                    key={option.text}
-                    sx={{
-                      padding: '8px',
-                    }}
-                  >
-                    <Stack // Could not find proper way to set spacing between ListItemIcon and ListItemText
-                      direction="row"
-                      spacing="16px"
-                    >
-                      <option.Icon
-                        sx={{
-                          height: '16px',
-                          margin: 0,
-                          color: (theme: Theme) => theme?.palette?.primary?.dark,
-                        }}
-                      />
-                      <ListItemText
-                        primary={option.text}
-                        primaryTypographyProps={{
-                          fontSize: '12px',
-                          lineHeight: '16px',
-                        }}
-                        sx={{
-                          height: '16px',
-                          margin: 0,
-                        }}
-                      />
-                    </Stack>
-                  </ListItemButton>
-                ))}
-              </List>
+              <Divider sx={{ marginX: '16px' }} />
+              <PopoverList items={additionalOptions} />
             </>
           )}
         </Stack>
@@ -511,11 +468,12 @@
   const smallRadius = '5px';
   return {
     backgroundColor: 'white',
-    padding: '16px',
+    padding: '0px',
     boxShadow: '3px 3px 7px #00000029',
     borderRadius: largeRadius,
     borderStartStartRadius: position === 'start' ? smallRadius : largeRadius,
     borderStartEndRadius: position === 'end' ? smallRadius : largeRadius,
+    overflow: 'hidden',
   };
 });
 
diff --git a/client/src/components/PopoverList.tsx b/client/src/components/PopoverList.tsx
new file mode 100644
index 0000000..7c81344
--- /dev/null
+++ b/client/src/components/PopoverList.tsx
@@ -0,0 +1,107 @@
+/*
+ * 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 {
+  ListItemText,
+  ListItemTextProps,
+  MenuItem,
+  MenuItemProps,
+  MenuList,
+  MenuListProps,
+  MenuProps,
+  Stack,
+  styled,
+  SvgIconProps,
+} from '@mui/material';
+import { ComponentType } from 'react';
+
+export type PopoverListItemData = {
+  label: string;
+  Icon: ComponentType<SvgIconProps>;
+  onClick: () => void;
+};
+
+interface ListIconProps extends SvgIconProps {
+  Icon: ComponentType<SvgIconProps>;
+}
+
+const ListIcon = styled(({ Icon, ...props }: ListIconProps) => <Icon {...props} />)(({ theme }) => ({
+  height: '16px',
+  fontSize: '16px',
+  color: theme?.palette?.primary?.dark,
+}));
+
+interface ListLabelProps extends ListItemTextProps {
+  label: string;
+}
+
+const ListLabel = styled(({ label, ...props }: ListLabelProps) => (
+  <ListItemText
+    {...props}
+    primary={label}
+    primaryTypographyProps={{
+      fontSize: '12px',
+      lineHeight: '16px',
+    }}
+  />
+))(() => ({
+  height: '16px',
+}));
+
+interface PopoverListItemProps extends MenuItemProps {
+  item: PopoverListItemData;
+  closeMenu?: MenuProps['onClose'];
+}
+
+const PopoverListItem = styled(({ item, closeMenu, ...props }: PopoverListItemProps) => (
+  <MenuItem
+    {...props}
+    onClick={() => {
+      item.onClick();
+      closeMenu?.({}, 'backdropClick');
+    }}
+    sx={{
+      paddingY: '11px',
+      paddingX: '16px',
+    }}
+  >
+    <Stack direction="row" spacing="10px">
+      <ListIcon Icon={item.Icon} />
+      <ListLabel label={item.label} />
+    </Stack>
+  </MenuItem>
+))(() => ({
+  // Failed to modify the styles from here
+}));
+
+interface PopoverListProps extends MenuListProps {
+  items: PopoverListItemData[];
+  onClose?: MenuProps['onClose'];
+}
+
+// A list intended to be used as a menu into a popover
+const PopoverList = styled(({ items, onClose, ...props }: PopoverListProps) => (
+  <MenuList {...props}>
+    {items.map((item, index) => (
+      <PopoverListItem key={index} item={item} closeMenu={onClose} />
+    ))}
+  </MenuList>
+))(() => ({
+  padding: '3px 0px',
+}));
+
+export default PopoverList;
diff --git a/client/src/components/RulesDialog.tsx b/client/src/components/RulesDialog.tsx
deleted file mode 100644
index 3b65f33..0000000
--- a/client/src/components/RulesDialog.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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 { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
-import { useTranslation } from 'react-i18next';
-
-interface RulesDialogProps {
-  openDialog: boolean;
-  title: string;
-  closeDialog: () => void;
-  children: React.ReactNode;
-}
-
-export default function RulesDialog(props: RulesDialogProps) {
-  const { t } = useTranslation();
-
-  return (
-    <Dialog open={props.openDialog} onClose={props.closeDialog}>
-      <DialogTitle>
-        {props.title}
-        <br />
-      </DialogTitle>
-      <DialogContent>{props.children}</DialogContent>
-      <DialogActions>
-        <Button onClick={props.closeDialog} autoFocus>
-          {t('dialog_close')}
-        </Button>
-      </DialogActions>
-    </Dialog>
-  );
-}
diff --git a/client/src/locale/en/translation.json b/client/src/locale/en/translation.json
index 8a2ee50..9774ddc 100644
--- a/client/src/locale/en/translation.json
+++ b/client/src/locale/en/translation.json
@@ -9,13 +9,27 @@
   "conversation_start_videocall": "Start video call",
   "conversation_close": "Close this conversation",
   "conversation_details": "Conversation details",
-  "conversation_block_contact": "Block this contact",
-  "conversation_delete_contact": "Delete this contact",
+  "conversation_block": "Block conversation",
+  "conversation_delete": "Remove conversation",
+  "conversation_details_username": "Username",
+  "conversation_details_identifier": "Identifier",
+  "conversation_details_qr_code": "QR code",
+  "conversation_details_is_swarm": "Is swarm",
+  "conversation_details_is_swarm_true": "True",
+  "conversation_details_is_swarm_false": "False",
+  "conversation_details_informations": "Informations",
+  "dialog_confirm_title_default": "Confirm action",
+  "conversation_ask_confirm_block": "Would you really like to block this conversation?",
+  "conversation_confirm_block": "Block",
+  "conversation_ask_confirm_remove": "Would you really like to remove this conversation?",
+  "conversation_confirm_remove": "Remove",
   "conversation_title_one": "{{member0}}",
   "conversation_title_two": "{{member0}} and {{member1}}",
   "conversation_title_three": "{{member0}}, {{member1}} and {{member2}}",
   "conversation_title_four": "{{member0}}, {{member1}}, {{member2}}, +1 other member",
   "conversation_title_more": "{{member0}}, {{member1}}, {{member2}}, +{{excess}} other members",
+  "dialog_close": "Close",
+  "dialog_cancel": "Cancel",
   "logout": "Log out",
   "username_input_helper_text_success": "Username available",
   "username_input_helper_text_taken": "Username already taken",
@@ -48,8 +62,7 @@
   "message_call_incoming": "Incoming call - {{duration}}",
   "message_swarm_created": "Swarm created",
   "message_user_joined": "{{user}} joined",
-  "messages_scroll_to_end": "",
-  "dialog_close": "Close",
+  "messages_scroll_to_end": "Scroll to end of conversation",
   "message_input_placeholder_one": "Write to {{member0}}",
   "message_input_placeholder_two": "Write to {{member0}} and {{member1}}",
   "message_input_placeholder_three": "Write to {{member0}}, {{member1}} and {{member2}}",
@@ -58,8 +71,8 @@
   "conversation_add_contact": "Add contact",
   "calling": "Calling...",
   "connecting": "Connecting...",
-  "incoming_call_{medium}": "",
   "end_call": "End call",
+  "incoming_call_medium": "",
   "login_username_not_found": "Username not found",
   "login_invalid_password": "Incorrect password",
   "login_form_title": "Login",
@@ -83,10 +96,5 @@
   "setup_login_password_placeholder_repeat": "Repeat password",
   "admin_creation_submit_button": "Create admin account",
   "severity_error": "Error",
-  "severity_success": "Success",
-  "incoming_call_audio": "Incoming audio call from {{member0}}",
-  "incoming_call_video": "Incoming video call from {{member0}}",
-  "refuse_call": "Refuse",
-  "accept_call_audio": "Accept in audio",
-  "accept_call_video": "Accept in video"
+  "severity_success": "Success"
 }
diff --git a/client/src/locale/fr/translation.json b/client/src/locale/fr/translation.json
index 272b4f1..9cd572d 100644
--- a/client/src/locale/fr/translation.json
+++ b/client/src/locale/fr/translation.json
@@ -9,13 +9,27 @@
   "conversation_start_videocall": "Démarrer appel vidéo",
   "conversation_close": "Fermer la conversation",
   "conversation_details": "Détails de la conversation",
-  "conversation_block_contact": "Bloquer le contact",
-  "conversation_delete_contact": "Supprimer le contact",
+  "conversation_block": "Bloquer la conversation",
+  "conversation_delete": "Supprimer la conversation",
+  "conversation_details_username": "Nom d'utilisateur",
+  "conversation_details_identifier": "Identifiant",
+  "conversation_details_qr_code": "Code QR",
+  "conversation_details_is_swarm": "Est un swarm",
+  "conversation_details_is_swarm_true": "Oui",
+  "conversation_details_is_swarm_false": "Non",
+  "conversation_details_informations": "Informations",
+  "dialog_confirm_title_default": "Merci de confirmer",
+  "conversation_ask_confirm_block": "Souhaitez-vous vraiment bloquer cette conversation ?",
+  "conversation_confirm_block": "Bloquer",
+  "conversation_ask_confirm_remove": "Souhaitez-vous vraiment supprimer cette conversation ?",
+  "conversation_confirm_remove": "Supprimer",
   "conversation_title_one": "{{member0}}",
   "conversation_title_two": "{{member0}} et {{member1}}",
   "conversation_title_three": "{{member0}}, {{member1}} et {{member2}}",
   "conversation_title_four": "{{member0}}, {{member1}}, {{member2}}, +1 autre membre",
   "conversation_title_more": "{{member0}}, {{member1}}, {{member2}}, +{{excess}} autres membres",
+  "dialog_close": "Fermer",
+  "dialog_cancel": "Annuler",
   "logout": "Se déconnecter",
   "username_input_helper_text_success": "Nom d'utilisateur disponible",
   "username_input_helper_text_taken": "Nom d'utilisateur déjà pris",
@@ -48,8 +62,7 @@
   "message_call_incoming": "Appel sortant - {{duration}}",
   "message_swarm_created": "Le Swarm a été créé",
   "message_user_joined": "{{user}} s'est joint",
-  "messages_scroll_to_end": "",
-  "dialog_close": "Fermer",
+  "messages_scroll_to_end": "Faire défiler jusqu'à la fin de la conversation",
   "message_input_placeholder_one": "Écrire à {{member0}}",
   "message_input_placeholder_two": "Écrire à {{member0}} et {{member1}}",
   "message_input_placeholder_three": "Écrire à {{member0}}, {{member1}} et {{member2}}",
@@ -58,8 +71,8 @@
   "conversation_add_contact": "Ajouter le contact",
   "calling": "Appel en cours...",
   "connecting": "Connexion en cours...",
-  "incoming_call_{medium}": "",
   "end_call": "Fin d'appel",
+  "incoming_call_medium": "",
   "login_username_not_found": "Nom d'utilisateur introuvable",
   "login_invalid_password": "Mot de passe incorrect",
   "login_form_title": "Connexion",
@@ -83,10 +96,5 @@
   "setup_login_password_placeholder_repeat": "Répéter le mot de passe",
   "admin_creation_submit_button": "Créer un compte admin",
   "severity_error": "Erreur",
-  "severity_success": "Succès",
-  "incoming_call_audio": "Appel audio entrant de {{member0}}",
-  "incoming_call_video": "Appel vidéo entrant de {{member0}}",
-  "refuse_call": "Refuser",
-  "accept_call_audio": "Accepter en audio",
-  "accept_call_video": "Accepter en vidéo"
+  "severity_success": "Succès"
 }
diff --git a/client/src/themes/Default.ts b/client/src/themes/Default.ts
index 60fb1ec..977de0b 100644
--- a/client/src/themes/Default.ts
+++ b/client/src/themes/Default.ts
@@ -205,6 +205,38 @@
         },
       },
     },
+    MuiDialog: {
+      styleOverrides: {
+        paper: {
+          padding: '16px',
+          boxShadow: '3px 3px 7px #00000029',
+          borderRadius: '20px',
+        },
+      },
+    },
+    MuiDialogActions: {
+      styleOverrides: {
+        root: {
+          padding: '0px',
+        },
+      },
+    },
+    MuiDialogContent: {
+      styleOverrides: {
+        root: {
+          padding: '0px',
+          margin: '16px 0px',
+          minWidth: '500px',
+        },
+      },
+    },
+    MuiDialogTitle: {
+      styleOverrides: {
+        root: {
+          padding: '0px',
+        },
+      },
+    },
     MuiSwitch: {
       defaultProps: {
         disableRipple: true,