Fix add contact and create MessengerProvider
Move all messenger logic and state from `Messenger.tsx` to a new context `MessengerProvider.tsx`.
Improve add contact logic to use a state in `MessengerContext` for the newContactId instead of a URL parameter. Remove `add-contact` route.
Improve messenger routing.
Fix remove contact.
GitLab: #171
Change-Id: Iea641deb12dbd339d03eff683b41834123a516ab
diff --git a/client/src/components/ConversationList.tsx b/client/src/components/ConversationList.tsx
index 316792b..684c7e5 100644
--- a/client/src/components/ConversationList.tsx
+++ b/client/src/components/ConversationList.tsx
@@ -20,17 +20,17 @@
import ListSubheader from '@mui/material/ListSubheader';
import Typography from '@mui/material/Typography';
import { Conversation } from 'jami-web-common';
-import { useEffect } from 'react';
+import { useContext, useEffect } from 'react';
+import { MessengerContext } from '../contexts/MessengerProvider';
import { useAppSelector } from '../redux/hooks';
import ConversationListItem from './ConversationListItem';
type ConversationListProps = {
- accountId: string;
conversations: Conversation[];
- search?: Conversation;
};
-export default function ConversationList(props: ConversationListProps) {
+export default function ConversationList({ conversations }: ConversationListProps) {
+ const { searchResult } = useContext(MessengerContext);
const { refresh } = useAppSelector((state) => state.userInfo);
useEffect(() => {
@@ -40,17 +40,17 @@
return (
<div className="rooms-list">
<List>
- {props.search instanceof Conversation && (
+ {searchResult && (
<div>
<ListSubheader>Public directory</ListSubheader>
- <ConversationListItem conversation={props.search} />
+ <ConversationListItem conversation={searchResult} />
<ListSubheader>Conversations</ListSubheader>
</div>
)}
- {props.conversations.map((conversation) => (
+ {conversations.map((conversation) => (
<ConversationListItem key={conversation.getId()} conversation={conversation} />
))}
- {props.conversations.length === 0 && (
+ {conversations.length === 0 && (
<div className="list-placeholder">
<GroupIcon color="disabled" fontSize="large" />
<Typography className="subtitle" variant="subtitle2">
diff --git a/client/src/components/ConversationListItem.tsx b/client/src/components/ConversationListItem.tsx
index 48e9004..03685c3 100644
--- a/client/src/components/ConversationListItem.tsx
+++ b/client/src/components/ConversationListItem.tsx
@@ -18,14 +18,17 @@
import { Box, ListItem, ListItemAvatar, ListItemText } from '@mui/material';
import { Conversation } from 'jami-web-common';
import { QRCodeCanvas } from 'qrcode.react';
-import { useCallback, useMemo, useState } from 'react';
+import { useCallback, useContext, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { useNavigate, useParams } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthProvider';
+import { MessengerContext } from '../contexts/MessengerProvider';
import { useStartCall } from '../hooks/useStartCall';
+import { useUrlParams } from '../hooks/useUrlParams';
import { setRefreshFromSlice } from '../redux/appSlice';
import { useAppDispatch } from '../redux/hooks';
+import { ConversationRouteParams } from '../router';
import ContextMenu, { ContextMenuHandler, useContextMenuHandler } from './ContextMenu';
import ConversationAvatar from './ConversationAvatar';
import { ConfirmationDialog, DialogContentList, InfosDialog, useDialogHandler } from './Dialog';
@@ -45,29 +48,37 @@
};
export default function ConversationListItem({ conversation }: ConversationListItemProps) {
- const { conversationId, contactId } = useParams();
+ const {
+ urlParams: { conversationId },
+ } = useUrlParams<ConversationRouteParams>();
const contextMenuHandler = useContextMenuHandler();
+ const { newContactId, setNewContactId } = useContext(MessengerContext);
- const pathId = conversationId || contactId;
+ const pathId = conversationId || newContactId;
const isSelected = conversation.getDisplayUri() === pathId;
+
const navigate = useNavigate();
const userId = conversation?.getFirstMember()?.contact.getUri();
- // TODO: Improve this component. conversationId should never be undefined.
- // (https://git.jami.net/savoirfairelinux/jami-web/-/issues/171)
- const uri = conversation.getId()
- ? `/conversation/${conversation.getId()}`
- : `/conversation/add-contact?newContactId=${userId}`;
+ const onClick = useCallback(() => {
+ const newConversationId = conversation.getId();
+ if (newConversationId) {
+ navigate(`/conversation/${newConversationId}`);
+ } else {
+ setNewContactId(userId);
+ }
+ }, [navigate, conversation, userId, setNewContactId]);
+
return (
<Box onContextMenu={contextMenuHandler.handleAnchorPosition}>
<ConversationMenu
userId={userId}
conversation={conversation}
- uri={uri}
+ onMessageClick={onClick}
isSelected={isSelected}
contextMenuProps={contextMenuHandler.props}
/>
- <ListItem button alignItems="flex-start" selected={isSelected} onClick={() => navigate(uri)}>
+ <ListItem button alignItems="flex-start" selected={isSelected} onClick={onClick}>
<ListItemAvatar>
<ConversationAvatar displayName={conversation.getDisplayNameNoFallback()} />
</ListItemAvatar>
@@ -80,12 +91,18 @@
interface ConversationMenuProps {
userId: string;
conversation: Conversation;
- uri: string;
+ onMessageClick: () => void;
isSelected: boolean;
contextMenuProps: ContextMenuHandler['props'];
}
-const ConversationMenu = ({ userId, conversation, uri, isSelected, contextMenuProps }: ConversationMenuProps) => {
+const ConversationMenu = ({
+ userId,
+ conversation,
+ onMessageClick,
+ isSelected,
+ contextMenuProps,
+}: ConversationMenuProps) => {
const { t } = useTranslation();
const { axiosInstance } = useAuthContext();
const [isSwarm] = useState(true);
@@ -117,9 +134,7 @@
{
label: t('conversation_message'),
Icon: MessageIcon,
- onClick: () => {
- navigate(uri);
- },
+ onClick: onMessageClick,
},
{
label: t('conversation_start_audiocall'),
@@ -177,7 +192,7 @@
],
[
navigate,
- uri,
+ onMessageClick,
isSelected,
getContactDetails,
detailsDialogHandler,
@@ -302,7 +317,7 @@
const remove = async () => {
const controller = new AbortController();
try {
- await axiosInstance.delete(`/contacts/${userId}/remove`, {
+ await axiosInstance.delete(`/contacts/${userId}`, {
signal: controller.signal,
});
dispatch(setRefreshFromSlice());
diff --git a/client/src/components/NewContactForm.tsx b/client/src/components/NewContactForm.tsx
index d55b80b..21ead27 100644
--- a/client/src/components/NewContactForm.tsx
+++ b/client/src/components/NewContactForm.tsx
@@ -17,28 +17,21 @@
*/
import { SearchRounded } from '@mui/icons-material';
import { InputAdornment, InputBase } from '@mui/material';
-import { ChangeEvent, FormEvent, useState } from 'react';
+import { ChangeEvent, useContext, useState } from 'react';
-type NewContactFormProps = {
- onChange?: (v: string) => void;
- onSubmit?: (v: string) => void;
-};
+import { MessengerContext } from '../contexts/MessengerProvider';
-export default function NewContactForm(props: NewContactFormProps) {
+export default function NewContactForm() {
+ const { setSearchQuery } = useContext(MessengerContext);
const [value, setValue] = useState('');
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
- if (props.onChange) props.onChange(event.target.value);
- };
-
- const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
- event.preventDefault();
- if (value && props.onSubmit) props.onSubmit(value);
+ setSearchQuery(event.target.value);
};
return (
- <form className="main-search" onSubmit={handleSubmit} noValidate autoComplete="off">
+ <form className="main-search" noValidate autoComplete="off">
<InputBase
className="main-search-input"
type="search"