blob: 41cfbe4491fd0205d8a5920abb64fa95ff901301 [file] [log] [blame]
simon575c9402022-10-25 16:21:40 -04001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
idillonef9ab812022-11-18 13:46:24 -050018import { Box, ListItem, ListItemAvatar, ListItemText } from '@mui/material';
simon575c9402022-10-25 16:21:40 -040019import { QRCodeCanvas } from 'qrcode.react';
simon21f7d9f2022-11-28 14:21:54 -050020import { useCallback, useContext, useMemo, useState } from 'react';
simon4e7445c2022-11-16 21:18:46 -050021import { useTranslation } from 'react-i18next';
simon21f7d9f2022-11-28 14:21:54 -050022import { useNavigate } from 'react-router-dom';
simon575c9402022-10-25 16:21:40 -040023
simon5da8ca62022-11-09 15:21:25 -050024import { useAuthContext } from '../contexts/AuthProvider';
simon09fe4822022-11-30 23:36:25 -050025import { useConversationContext } from '../contexts/ConversationProvider';
simon21f7d9f2022-11-28 14:21:54 -050026import { MessengerContext } from '../contexts/MessengerProvider';
simonff1cb352022-11-24 15:15:26 -050027import { useStartCall } from '../hooks/useStartCall';
Misha Krieger-Raynauld6bbdacf2022-11-29 21:45:40 -050028import { Conversation } from '../models/Conversation';
simon575c9402022-10-25 16:21:40 -040029import { setRefreshFromSlice } from '../redux/appSlice';
30import { useAppDispatch } from '../redux/hooks';
idillonef9ab812022-11-18 13:46:24 -050031import ContextMenu, { ContextMenuHandler, useContextMenuHandler } from './ContextMenu';
simon575c9402022-10-25 16:21:40 -040032import ConversationAvatar from './ConversationAvatar';
idillonef9ab812022-11-18 13:46:24 -050033import { ConfirmationDialog, DialogContentList, InfosDialog, useDialogHandler } from './Dialog';
34import { PopoverListItemData } from './PopoverList';
simon5da8ca62022-11-09 15:21:25 -050035import {
36 AudioCallIcon,
37 BlockContactIcon,
38 CancelIcon,
39 ContactDetailsIcon,
40 MessageIcon,
41 RemoveContactIcon,
42 VideoCallIcon,
43} from './SvgIcon';
simon575c9402022-10-25 16:21:40 -040044
simon575c9402022-10-25 16:21:40 -040045type ConversationListItemProps = {
46 conversation: Conversation;
47};
48
49export default function ConversationListItem({ conversation }: ConversationListItemProps) {
simon09fe4822022-11-30 23:36:25 -050050 const conversationContext = useConversationContext(true);
51 const conversationId = conversationContext?.conversationId;
idillonef9ab812022-11-18 13:46:24 -050052 const contextMenuHandler = useContextMenuHandler();
simon21f7d9f2022-11-28 14:21:54 -050053 const { newContactId, setNewContactId } = useContext(MessengerContext);
simon575c9402022-10-25 16:21:40 -040054
simon09fe4822022-11-30 23:36:25 -050055 const pathId = conversationId ?? newContactId;
simon575c9402022-10-25 16:21:40 -040056 const isSelected = conversation.getDisplayUri() === pathId;
simon21f7d9f2022-11-28 14:21:54 -050057
simon575c9402022-10-25 16:21:40 -040058 const navigate = useNavigate();
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -050059 const userId = conversation?.getFirstMember()?.contact.getUri();
simonff1cb352022-11-24 15:15:26 -050060
simon21f7d9f2022-11-28 14:21:54 -050061 const onClick = useCallback(() => {
62 const newConversationId = conversation.getId();
63 if (newConversationId) {
64 navigate(`/conversation/${newConversationId}`);
65 } else {
66 setNewContactId(userId);
67 }
68 }, [navigate, conversation, userId, setNewContactId]);
69
idillonef9ab812022-11-18 13:46:24 -050070 return (
71 <Box onContextMenu={contextMenuHandler.handleAnchorPosition}>
72 <ConversationMenu
73 userId={userId}
74 conversation={conversation}
simon21f7d9f2022-11-28 14:21:54 -050075 onMessageClick={onClick}
idillonef9ab812022-11-18 13:46:24 -050076 isSelected={isSelected}
77 contextMenuProps={contextMenuHandler.props}
78 />
simon21f7d9f2022-11-28 14:21:54 -050079 <ListItem button alignItems="flex-start" selected={isSelected} onClick={onClick}>
idillonef9ab812022-11-18 13:46:24 -050080 <ListItemAvatar>
81 <ConversationAvatar displayName={conversation.getDisplayNameNoFallback()} />
82 </ListItemAvatar>
83 <ListItemText primary={conversation.getDisplayName()} secondary={conversation.getDisplayUri()} />
84 </ListItem>
85 </Box>
86 );
87}
88
89interface ConversationMenuProps {
90 userId: string;
91 conversation: Conversation;
simon21f7d9f2022-11-28 14:21:54 -050092 onMessageClick: () => void;
idillonef9ab812022-11-18 13:46:24 -050093 isSelected: boolean;
94 contextMenuProps: ContextMenuHandler['props'];
95}
96
simon21f7d9f2022-11-28 14:21:54 -050097const ConversationMenu = ({
98 userId,
99 conversation,
100 onMessageClick,
101 isSelected,
102 contextMenuProps,
103}: ConversationMenuProps) => {
idillonef9ab812022-11-18 13:46:24 -0500104 const { t } = useTranslation();
105 const { axiosInstance } = useAuthContext();
simon416d0792022-11-03 02:46:18 -0400106 const [isSwarm] = useState(true);
simon575c9402022-10-25 16:21:40 -0400107
idillonef9ab812022-11-18 13:46:24 -0500108 const detailsDialogHandler = useDialogHandler();
109 const blockContactDialogHandler = useDialogHandler();
110 const removeContactDialogHandler = useDialogHandler();
simon575c9402022-10-25 16:21:40 -0400111
idillonef9ab812022-11-18 13:46:24 -0500112 const navigate = useNavigate();
113
simonff1cb352022-11-24 15:15:26 -0500114 const startCall = useStartCall();
115
idillonef9ab812022-11-18 13:46:24 -0500116 const getContactDetails = useCallback(async () => {
simon575c9402022-10-25 16:21:40 -0400117 const controller = new AbortController();
simon94fe53e2022-11-10 12:51:58 -0500118 try {
Misha Krieger-Raynauld46e9d242022-11-12 18:02:43 -0500119 const { data } = await axiosInstance.get(`/contacts/${userId}`, {
simon94fe53e2022-11-10 12:51:58 -0500120 signal: controller.signal,
121 });
122 console.log('CONTACT LIST - DETAILS: ', data);
123 } catch (e) {
124 console.log('ERROR GET CONTACT DETAILS: ', e);
125 }
idillonef9ab812022-11-18 13:46:24 -0500126 }, [axiosInstance, userId]);
simon575c9402022-10-25 16:21:40 -0400127
simonff1cb352022-11-24 15:15:26 -0500128 const conversationId = conversation.getId();
129
idillonef9ab812022-11-18 13:46:24 -0500130 const menuOptions: PopoverListItemData[] = useMemo(
131 () => [
132 {
133 label: t('conversation_message'),
134 Icon: MessageIcon,
simon21f7d9f2022-11-28 14:21:54 -0500135 onClick: onMessageClick,
idillonef9ab812022-11-18 13:46:24 -0500136 },
137 {
138 label: t('conversation_start_audiocall'),
139 Icon: AudioCallIcon,
140 onClick: () => {
simonff1cb352022-11-24 15:15:26 -0500141 if (conversationId) {
Charlie380dc5e2022-11-29 16:51:42 -0500142 startCall(conversationId);
simonff1cb352022-11-24 15:15:26 -0500143 }
idillonef9ab812022-11-18 13:46:24 -0500144 },
145 },
146 {
147 label: t('conversation_start_videocall'),
148 Icon: VideoCallIcon,
149 onClick: () => {
simonff1cb352022-11-24 15:15:26 -0500150 if (conversationId) {
Charlie380dc5e2022-11-29 16:51:42 -0500151 startCall(conversationId, {
simonff1cb352022-11-24 15:15:26 -0500152 isVideoOn: true,
153 });
154 }
idillonef9ab812022-11-18 13:46:24 -0500155 },
156 },
157 ...(isSelected
158 ? [
159 {
160 label: t('conversation_close'),
161 Icon: CancelIcon,
162 onClick: () => {
163 navigate(`/`);
164 },
165 },
166 ]
167 : []),
168 {
169 label: t('conversation_details'),
170 Icon: ContactDetailsIcon,
171 onClick: () => {
172 detailsDialogHandler.openDialog();
173 getContactDetails();
174 },
175 },
176 {
177 label: t('conversation_block'),
178 Icon: BlockContactIcon,
179 onClick: () => {
180 blockContactDialogHandler.openDialog();
181 },
182 },
183 {
184 label: t('conversation_delete'),
185 Icon: RemoveContactIcon,
186 onClick: () => {
187 removeContactDialogHandler.openDialog();
188 },
189 },
190 ],
191 [
idillonef9ab812022-11-18 13:46:24 -0500192 navigate,
simon21f7d9f2022-11-28 14:21:54 -0500193 onMessageClick,
idillonef9ab812022-11-18 13:46:24 -0500194 isSelected,
195 getContactDetails,
196 detailsDialogHandler,
197 blockContactDialogHandler,
198 removeContactDialogHandler,
199 t,
simonff1cb352022-11-24 15:15:26 -0500200 startCall,
201 conversationId,
idillonef9ab812022-11-18 13:46:24 -0500202 ]
203 );
simon575c9402022-10-25 16:21:40 -0400204
idillonef9ab812022-11-18 13:46:24 -0500205 return (
206 <>
207 <ContextMenu {...contextMenuProps} items={menuOptions} />
208
209 <DetailsDialog {...detailsDialogHandler.props} userId={userId} conversation={conversation} isSwarm={isSwarm} />
210
211 <RemoveContactDialog {...removeContactDialogHandler.props} userId={userId} conversation={conversation} />
212
213 <BlockContactDialog {...blockContactDialogHandler.props} userId={userId} conversation={conversation} />
214 </>
215 );
216};
217
218interface DetailsDialogProps {
219 userId: string;
220 conversation: Conversation;
221 open: boolean;
222 onClose: () => void;
223 isSwarm: boolean;
224}
225
226const DetailsDialog = ({ userId, conversation, open, onClose, isSwarm }: DetailsDialogProps) => {
227 const { t } = useTranslation();
228 const items = useMemo(
229 () => [
230 {
231 label: t('conversation_details_username'),
232 value: conversation.getDisplayNameNoFallback(),
233 },
234 {
235 label: t('conversation_details_identifier'),
236 value: userId,
237 },
238 {
239 label: t('conversation_details_qr_code'),
240 value: <QRCodeCanvas size={80} value={`${userId}`} />,
241 },
242 {
243 label: t('conversation_details_is_swarm'),
244 value: isSwarm ? t('conversation_details_is_swarm_true') : t('conversation_details_is_swarm_false'),
245 },
246 ],
247 [userId, conversation, isSwarm, t]
248 );
249 return (
250 <InfosDialog
251 open={open}
252 onClose={onClose}
253 icon={
254 <ConversationAvatar
255 sx={{ width: 'inherit', height: 'inherit' }}
256 displayName={conversation.getDisplayNameNoFallback()}
257 />
258 }
259 title={conversation.getDisplayNameNoFallback() || ''}
260 content={<DialogContentList title={t('conversation_details_informations')} items={items} />}
261 />
262 );
263};
264
265interface BlockContactDialogProps {
266 userId: string;
267 conversation: Conversation;
268 open: boolean;
269 onClose: () => void;
270}
271
272const BlockContactDialog = ({ userId, open, onClose }: BlockContactDialogProps) => {
273 const { axiosInstance } = useAuthContext();
274 const { t } = useTranslation();
275 const dispatch = useAppDispatch();
276
277 const block = async () => {
simon575c9402022-10-25 16:21:40 -0400278 const controller = new AbortController();
simon94fe53e2022-11-10 12:51:58 -0500279 try {
idillonef9ab812022-11-18 13:46:24 -0500280 await axiosInstance.post(`/contacts/${userId}/block`, {
simon94fe53e2022-11-10 12:51:58 -0500281 signal: controller.signal,
simon575c9402022-10-25 16:21:40 -0400282 });
simon94fe53e2022-11-10 12:51:58 -0500283 dispatch(setRefreshFromSlice());
284 } catch (e) {
idillonef9ab812022-11-18 13:46:24 -0500285 console.error(`Error $block contact : `, e);
simon94fe53e2022-11-10 12:51:58 -0500286 dispatch(setRefreshFromSlice());
287 }
idillonef9ab812022-11-18 13:46:24 -0500288 onClose();
simon575c9402022-10-25 16:21:40 -0400289 };
290
simon575c9402022-10-25 16:21:40 -0400291 return (
idillonef9ab812022-11-18 13:46:24 -0500292 <ConfirmationDialog
293 open={open}
294 onClose={onClose}
295 title={t('dialog_confirm_title_default')}
296 content={t('conversation_ask_confirm_block')}
297 onConfirm={block}
298 confirmButtonText={t('conversation_confirm_block')}
299 />
simon575c9402022-10-25 16:21:40 -0400300 );
idillonef9ab812022-11-18 13:46:24 -0500301};
302
303interface RemoveContactDialogProps {
304 userId: string;
305 conversation: Conversation;
306 open: boolean;
307 onClose: () => void;
simon575c9402022-10-25 16:21:40 -0400308}
idillonef9ab812022-11-18 13:46:24 -0500309
310const RemoveContactDialog = ({ userId, open, onClose }: RemoveContactDialogProps) => {
311 const { axiosInstance } = useAuthContext();
312 const { t } = useTranslation();
313 const dispatch = useAppDispatch();
314
315 const remove = async () => {
316 const controller = new AbortController();
317 try {
simon21f7d9f2022-11-28 14:21:54 -0500318 await axiosInstance.delete(`/contacts/${userId}`, {
idillonef9ab812022-11-18 13:46:24 -0500319 signal: controller.signal,
320 });
321 dispatch(setRefreshFromSlice());
322 } catch (e) {
323 console.error(`Error removing contact : `, e);
324 dispatch(setRefreshFromSlice());
325 }
326 onClose();
327 };
328
329 return (
330 <ConfirmationDialog
331 open={open}
332 onClose={onClose}
333 title={t('dialog_confirm_title_default')}
334 content={t('conversation_ask_confirm_remove')}
335 onConfirm={remove}
336 confirmButtonText={t('conversation_confirm_remove')}
337 />
338 );
339};