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'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;
+}