Decouple client from server

Add Vite dependency and remove server side rendering to make it
possible to run the client independently.
Remove webpack config, replace with the `Vite` build tool.

GitLab: #55
Change-Id: I3a05d2e86cf6cb0ab91e77b3696f393132137575
diff --git a/client/src/components/ConversationListItem.jsx b/client/src/components/ConversationListItem.jsx
new file mode 100644
index 0000000..16c1ccc
--- /dev/null
+++ b/client/src/components/ConversationListItem.jsx
@@ -0,0 +1,431 @@
+/*
+ * 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, ListItem, ListItemAvatar, ListItemText, Stack, Typography } from '@mui/material';
+import { Conversation } from 'jami-web-common';
+import { QRCodeCanvas } from 'qrcode.react';
+import { useState } from 'react';
+import Modal from 'react-modal';
+import { useNavigate, useParams } from 'react-router-dom';
+
+import authManager from '../AuthManager';
+import { setRefreshFromSlice } from '../redux/appSlice';
+import { useAppDispatch } from '../redux/hooks';
+import ConversationAvatar from './ConversationAvatar';
+import { RemoveContactIcon, VideoCallIcon } from './SvgIcon.tsx';
+import { AudioCallIcon, BlockContactIcon, ContactDetailsIcon, CrossIcon, MessageIcon } from './SvgIcon.tsx';
+
+const customStyles = {
+  content: {
+    // right: "auto",
+    // bottom: "auto",
+    // // marginRight: "-50%",
+    // transform: "translate(-50%, -50%)",
+    // padding: "16px"
+
+    // top: "1364px",
+    left: '94px',
+    width: '180px',
+    height: '262px',
+    background: '#FFFFFF 0% 0% no-repeat padding-box',
+    boxShadow: '3px 3px 7px #00000029',
+    borderRadius: '5px 20px 20px 20px',
+    opacity: '1',
+
+    textAlign: 'left',
+    font: 'normal normal normal 12px/26px Ubuntu',
+    letterSpacing: '0px',
+    color: '#000000',
+  },
+};
+
+const cancelStyles = {
+  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 = {
+  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 stackStyles = {
+  flexDirection: 'row',
+  marginBottom: '4px',
+  spacing: '40px',
+  flex: 1,
+  alignItems: 'center',
+};
+
+const iconTextStyle = {
+  marginRight: '10px',
+};
+
+const iconColor = '#005699';
+
+export default function ConversationListItem(props) {
+  const { conversationId, contactId } = useParams();
+  const dispatch = useAppDispatch();
+
+  const conversation = props.conversation;
+
+  const pathId = conversationId || contactId;
+  const isSelected = conversation.getDisplayUri() === pathId;
+  const navigate = useNavigate();
+
+  const [modalIsOpen, setIsOpen] = useState(false);
+  const [modalDetailsIsOpen, setModalDetailsIsOpen] = useState(false);
+  const [modalDeleteIsOpen, setModalDeleteIsOpen] = useState(false);
+  const [blockOrRemove, setBlockOrRemove] = useState(true);
+  const [userId, setUserId] = useState(conversation?.getFirstMember()?.contact.getUri());
+  const [isSwarm, setIsSwarm] = useState('true');
+
+  const openModal = (e) => {
+    e.preventDefault();
+    console.log(e);
+    setIsOpen(true);
+  };
+  const openModalDetails = () => setModalDetailsIsOpen(true);
+  const openModalDelete = () => setModalDeleteIsOpen(true);
+  const closeModal = () => setIsOpen(false);
+  const closeModalDetails = () => setModalDetailsIsOpen(false);
+  const closeModalDelete = () => setModalDeleteIsOpen(false);
+
+  let subtitle;
+  function afterOpenModal() {
+    // references are now sync'd and can be accessed.
+    subtitle.style.color = '#f00';
+  }
+
+  const getContactDetails = () => {
+    const controller = new AbortController();
+    authManager
+      .fetch(`/api/accounts/${conversation.getAccountId()}/contacts/details/${userId}`, {
+        signal: controller.signal,
+      })
+      .then((res) => res.json())
+      .then((result) => {
+        console.log('CONTACT LIST - DETAILS: ', result);
+      })
+      .catch((e) => console.log('ERROR GET CONTACT DETAILS: ', e));
+  };
+
+  const removeOrBlock = (typeOfRemove) => {
+    console.log(typeOfRemove);
+    setBlockOrRemove(false);
+
+    console.log('EEEH', typeOfRemove, conversation.getAccountId(), userId);
+
+    const controller = new AbortController();
+    authManager
+      .fetch(`/api/accounts/${conversation.getAccountId()}/contacts/${typeOfRemove}/${userId}`, {
+        signal: controller.signal,
+        method: 'DELETE',
+      })
+      .then((res) => res.json())
+      .then((result) => {
+        console.log('propre');
+        dispatch(setRefreshFromSlice());
+      })
+      .catch((e) => {
+        console.log(`ERROR ${typeOfRemove}ing CONTACT : `, e);
+        dispatch(setRefreshFromSlice());
+      });
+    closeModalDelete();
+  };
+
+  const uri = conversation.getId() ? `conversation/${conversation.getId()}` : `addContact/${userId}`;
+  if (conversation instanceof Conversation) {
+    return (
+      <div onContextMenu={openModal}>
+        <div name="Modal conversation">
+          <Modal
+            isOpen={modalIsOpen}
+            //   onAfterOpen={afterOpenModal}
+            onRequestClose={closeModal}
+            style={customStyles}
+            contentLabel="Example Modal"
+          >
+            <Stack
+              onClick={() => {
+                navigate(`/account/${conversation.getAccountId()}/${uri}`);
+                closeModal();
+              }}
+              {...stackStyles}
+            >
+              <div style={{ ...iconTextStyle }}>
+                <MessageIcon style={{ color: iconColor }} />
+              </div>
+              Message
+            </Stack>
+            <Stack {...stackStyles}>
+              <div style={{ ...iconTextStyle }}>
+                <AudioCallIcon style={{ color: iconColor }} />
+              </div>
+              Démarrer appel audio
+            </Stack>
+
+            <Stack {...stackStyles}>
+              <div style={{ ...iconTextStyle }}>
+                <VideoCallIcon style={{ color: iconColor }} />
+              </div>
+              Démarrer appel vidéo
+            </Stack>
+
+            <Stack
+              {...stackStyles}
+              onClick={() => {
+                navigate(`/account/${conversation.getAccountId()}/`);
+                closeModal();
+              }}
+            >
+              <div style={{ ...iconTextStyle }}>
+                <CrossIcon style={{ color: iconColor }} />
+              </div>
+              Fermer la conversation
+            </Stack>
+
+            <Stack
+              onClick={() => {
+                console.log('open details contact for: ');
+                closeModal();
+                openModalDetails();
+                getContactDetails();
+              }}
+              {...stackStyles}
+            >
+              <div style={{ ...iconTextStyle }}>
+                <ContactDetailsIcon style={{ color: iconColor }} />
+              </div>
+              Détails de la conversation
+            </Stack>
+
+            <Stack
+              onClick={() => {
+                setBlockOrRemove(true);
+                closeModal();
+                openModalDelete();
+              }}
+              {...stackStyles}
+            >
+              <div style={{ ...iconTextStyle }}>
+                <BlockContactIcon style={{ color: iconColor }} />
+              </div>
+              Bloquer le contact
+            </Stack>
+
+            <Stack
+              onClick={() => {
+                setBlockOrRemove(false);
+                closeModal();
+                openModalDelete();
+              }}
+              {...stackStyles}
+            >
+              <div style={{ ...iconTextStyle }}>
+                <RemoveContactIcon style={{ color: iconColor }} />
+              </div>
+              Supprimer contact
+            </Stack>
+          </Modal>
+        </div>
+
+        <div name="Contact details">
+          <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',
+                    direction: 'column',
+                    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 height={'40px'}>
+                  <QRCodeCanvas value={`${userId}`} />
+                </div>
+
+                <Typography variant="caption">
+                  <div style={{ fontWeight: 'bold' }}> {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 name="Remove or block details">
+          <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('block');
+                    else removeOrBlock('remove');
+                  }}
+                  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(`/account/${conversation.getAccountId()}/${uri}`)}
+        >
+          <ListItemAvatar>
+            <ConversationAvatar displayName={conversation.getDisplayNameNoFallback()} />
+          </ListItemAvatar>
+          <ListItemText primary={conversation.getDisplayName()} secondary={conversation.getDisplayUri()} />
+        </ListItem>
+      </div>
+    );
+  } else return null;
+}