Migrate client features to use new server

Remove all authManager references where possible.
Change fetch calls url to new server.

GitLab: #79
GitLab: #100
GitLab: #110
Change-Id: I1dce64108ceba67531372df764f8f7563cc50a3b
diff --git a/client/src/components/AccountPreferences.tsx b/client/src/components/AccountPreferences.tsx
index 8ca098e..f0c5d31 100644
--- a/client/src/components/AccountPreferences.tsx
+++ b/client/src/components/AccountPreferences.tsx
@@ -38,8 +38,8 @@
 import { Account, AccountDetails } from 'jami-web-common';
 import { useState } from 'react';
 
-import authManager from '../AuthManager';
 import { useAuthContext } from '../contexts/AuthProvider';
+import { apiUrl } from '../utils/constants';
 import ConversationAvatar from './ConversationAvatar';
 import ConversationsOverviewCard from './ConversationsOverviewCard';
 import JamiIdCard from './JamiIdCard';
@@ -64,7 +64,8 @@
 export default function AccountPreferences({ account: _account }: AccountPreferencesProps) {
   const authContext = useAuthContext(true);
   const account = _account ?? authContext?.account;
-  if (!account) {
+  const token = authContext?.token;
+  if (!account || !token) {
     throw new Error('Account not defined');
   }
 
@@ -83,23 +84,34 @@
 
   const addModerator = () => {
     if (defaultModeratorUri) {
-      authManager.fetch(`/api/accounts/${account.getId()}/defaultModerators/${defaultModeratorUri}`, { method: 'PUT' });
+      fetch(new URL(`/default-moderators/${defaultModeratorUri}`, apiUrl), {
+        headers: {
+          Authorization: `Bearer ${token}`,
+        },
+        method: 'PUT',
+      });
       setDefaultModeratorUri('');
     }
   };
 
   const removeModerator = (uri: string) =>
-    authManager.fetch(`/api/accounts/${account.getId()}/defaultModerators/${uri}`, { method: 'DELETE' });
+    fetch(new URL(`/default-moderators/${uri}`, apiUrl), {
+      headers: {
+        Authorization: `Bearer ${token}`,
+      },
+      method: 'DELETE',
+    });
 
   const handleToggle = (key: keyof AccountDetails, value: boolean) => {
     console.log(`handleToggle ${key} ${value}`);
     const newDetails: Partial<AccountDetails> = {};
     newDetails[key] = value ? 'true' : 'false';
     console.log(newDetails);
-    authManager.fetch(`/api/accounts/${account.getId()}`, {
-      method: 'POST',
+    fetch(new URL('/account', apiUrl), {
+      method: 'PATCH',
       headers: {
         Accept: 'application/json',
+        Authorization: `Bearer ${token}`,
         'Content-Type': 'application/json',
       },
       body: JSON.stringify(newDetails),
@@ -133,7 +145,7 @@
 
         <Grid item xs={12} sm={6}>
           <motion.div variants={thumbnailVariants}>
-            <ConversationsOverviewCard accountId={account.getId()} />
+            <ConversationsOverviewCard />
           </motion.div>
         </Grid>
 
diff --git a/client/src/components/ContactList.jsx b/client/src/components/ContactList.jsx
index c35c4f0..3bc040b 100644
--- a/client/src/components/ContactList.jsx
+++ b/client/src/components/ContactList.jsx
@@ -21,8 +21,9 @@
 import { useEffect, useState } from 'react';
 import Modal from 'react-modal';
 
-import authManager from '../AuthManager';
+import { useAuthContext } from '../contexts/AuthProvider';
 import { useAppDispatch, useAppSelector } from '../redux/hooks';
+import { apiUrl } from '../utils/constants';
 import ConversationAvatar from './ConversationAvatar';
 
 const customStyles = {
@@ -37,6 +38,7 @@
 };
 
 export default function ContactList() {
+  const { token } = useAuthContext();
   const { accountId } = useAppSelector((state) => state.userInfo);
   const dispatch = useAppDispatch();
 
@@ -59,10 +61,12 @@
 
   const getContactDetails = () => {
     const controller = new AbortController();
-    authManager
-      .fetch(`/api/accounts/${accountId}/contacts/details/${currentContact.id}`, {
-        signal: controller.signal,
-      })
+    fetch(new URL(`/contacts/${currentContact.id}`, apiUrl), {
+      signal: controller.signal,
+      headers: {
+        Authorization: `Bearer ${token}`,
+      },
+    })
       .then((res) => res.json())
       .then((result) => {
         console.log('CONTACT LIST - DETAILS: ', result);
@@ -70,33 +74,41 @@
       .catch((e) => console.log('ERROR GET CONTACT DETAILS: ', e));
   };
 
-  const removeOrBlock = (typeOfRemove) => {
+  const removeOrBlock = (block = false) => {
     console.log('REMOVE');
     setBlockOrRemove(false);
     const controller = new AbortController();
-    authManager
-      .fetch(`/api/accounts/${accountId}/contacts/${typeOfRemove}/${currentContact.id}`, {
-        signal: controller.signal,
-        method: 'DELETE',
-      })
+    let url = `/contacts/${currentContact.id}`;
+    if (block) {
+      url += '/block';
+    }
+    fetch(new URL(url, apiUrl), {
+      signal: controller.signal,
+      method: block ? 'POST' : 'DELETE',
+      headers: {
+        Authorization: `Bearer ${token}`,
+      },
+    })
       .then((res) => res.json())
-      .catch((e) => console.log(`ERROR ${typeOfRemove}ing CONTACT : `, e));
+      .catch((e) => console.log(`ERROR ${block ? 'blocking' : 'removing'} CONTACT : `, e));
     closeModalDelete();
   };
 
   useEffect(() => {
     const controller = new AbortController();
-    authManager
-      .fetch(`/api/accounts/${accountId}/contacts`, {
-        signal: controller.signal,
-      })
+    fetch(new URL(`/contacts`, apiUrl), {
+      signal: controller.signal,
+      headers: {
+        Authorization: `Bearer ${token}`,
+      },
+    })
       .then((res) => res.json())
       .then((result) => {
         console.log('CONTACTS: ', result);
         setContacts(result);
       });
     return () => controller.abort();
-  }, [accountId, blockOrRemove]);
+  }, [token]);
 
   return (
     <div className="rooms-list">
@@ -165,9 +177,9 @@
         Voulez vous vraiment {blockOrRemove ? 'bloquer' : 'supprimer'} ce contact?
         <br />
         {blockOrRemove ? (
-          <button onClick={() => removeOrBlock('block')}>Bloquer</button>
+          <button onClick={() => removeOrBlock(true)}>Bloquer</button>
         ) : (
-          <button onClick={() => removeOrBlock('remove')}>Supprimer</button>
+          <button onClick={() => removeOrBlock()}>Supprimer</button>
         )}
         <button onClick={closeModalDelete}>Annuler</button>
       </Modal>
diff --git a/client/src/components/ConversationListItem.tsx b/client/src/components/ConversationListItem.tsx
index 9a928e7..c094d71 100644
--- a/client/src/components/ConversationListItem.tsx
+++ b/client/src/components/ConversationListItem.tsx
@@ -33,12 +33,20 @@
 import Modal from 'react-modal';
 import { useNavigate, useParams } from 'react-router-dom';
 
-import authManager from '../AuthManager';
+import { useAuthContext } from '../contexts/AuthProvider';
 import { setRefreshFromSlice } from '../redux/appSlice';
 import { useAppDispatch } from '../redux/hooks';
+import { apiUrl } from '../utils/constants';
 import ConversationAvatar from './ConversationAvatar';
-import { CancelIcon, RemoveContactIcon, VideoCallIcon } from './SvgIcon';
-import { AudioCallIcon, BlockContactIcon, ContactDetailsIcon, MessageIcon } from './SvgIcon';
+import {
+  AudioCallIcon,
+  BlockContactIcon,
+  CancelIcon,
+  ContactDetailsIcon,
+  MessageIcon,
+  RemoveContactIcon,
+  VideoCallIcon,
+} from './SvgIcon';
 
 const cancelStyles: Modal.Styles = {
   content: {
@@ -81,6 +89,7 @@
 };
 
 export default function ConversationListItem({ conversation }: ConversationListItemProps) {
+  const { token } = useAuthContext();
   const { conversationId, contactId } = useParams();
   const dispatch = useAppDispatch();
 
@@ -95,8 +104,6 @@
   const [userId] = useState(conversation?.getFirstMember()?.contact.getUri());
   const [isSwarm] = useState(true);
 
-  const navigateUrlPrefix = `/deprecated-account/${conversation.getAccountId()}`;
-
   const openMenu = (e: MouseEvent<HTMLDivElement>) => {
     e.preventDefault();
     console.log(e);
@@ -110,10 +117,12 @@
 
   const getContactDetails = () => {
     const controller = new AbortController();
-    authManager
-      .fetch(`/api/accounts/${conversation.getAccountId()}/contacts/details/${userId}`, {
-        signal: controller.signal,
-      })
+    fetch(new URL(`/contacts/${userId}`, apiUrl), {
+      signal: controller.signal,
+      headers: {
+        Authorization: `Bearer ${token}`,
+      },
+    })
       .then((res) => res.json())
       .then((result) => {
         console.log('CONTACT LIST - DETAILS: ', result);
@@ -121,38 +130,43 @@
       .catch((e) => console.log('ERROR GET CONTACT DETAILS: ', e));
   };
 
-  const removeOrBlock = (typeOfRemove: 'block' | 'remove') => {
-    console.log(typeOfRemove);
+  const removeOrBlock = (block = false) => {
     setBlockOrRemove(false);
 
-    console.log('EEEH', typeOfRemove, conversation.getAccountId(), userId);
+    console.log('EEEH', conversation.getAccountId(), userId);
 
     const controller = new AbortController();
-    authManager
-      .fetch(`/api/accounts/${conversation.getAccountId()}/contacts/${typeOfRemove}/${userId}`, {
-        signal: controller.signal,
-        method: 'DELETE',
-      })
+    let url = `/contacts/${userId}`;
+    if (block) {
+      url += '/block';
+    }
+    fetch(new URL(url, apiUrl), {
+      signal: controller.signal,
+      method: block ? 'POST' : 'DELETE',
+      headers: {
+        Authorization: `Bearer ${token}`,
+      },
+    })
       .then((res) => res.json())
       .then(() => {
         console.log('propre');
         dispatch(setRefreshFromSlice());
       })
       .catch((e) => {
-        console.log(`ERROR ${typeOfRemove}ing CONTACT : `, e);
+        console.log(`ERROR ${block ? 'blocking' : 'removing'} CONTACT : `, e);
         dispatch(setRefreshFromSlice());
       });
     closeModalDelete();
   };
 
-  const uri = conversation.getId() ? `conversation/${conversation.getId()}` : `addContact/${userId}`;
+  const uri = conversation.getId() ? `/account/conversation/${conversation.getId()}` : `/account/addContact/${userId}`;
   return (
     <div onContextMenu={openMenu}>
       <div>
         <Menu open={!!menuAnchorEl} onClose={closeModal} anchorEl={menuAnchorEl}>
           <MenuItem
             onClick={() => {
-              navigate(`${navigateUrlPrefix}/${uri}`);
+              navigate(uri);
               closeModal();
             }}
           >
@@ -165,7 +179,7 @@
           </MenuItem>
           <MenuItem
             onClick={() => {
-              navigate(`${navigateUrlPrefix}/call/${conversation.getId()}`);
+              navigate(`/account/call/${conversation.getId()}`);
             }}
           >
             <ListItemIcon>
@@ -178,7 +192,7 @@
 
           <MenuItem
             onClick={() => {
-              navigate(`${navigateUrlPrefix}/call/${conversation.getId()}?video=true`);
+              navigate(`call/${conversation.getId()}?video=true`);
             }}
           >
             <ListItemIcon>
@@ -192,7 +206,7 @@
           {isSelected && (
             <MenuItem
               onClick={() => {
-                navigate(`${navigateUrlPrefix}/`);
+                navigate(`/account`);
                 closeModal();
               }}
             >
@@ -366,8 +380,8 @@
             <Stack direction={'row'} top={'25px'} alignSelf="center" spacing={1}>
               <Box
                 onClick={() => {
-                  if (blockOrRemove) removeOrBlock('block');
-                  else removeOrBlock('remove');
+                  if (blockOrRemove) removeOrBlock(true);
+                  else removeOrBlock(false);
                 }}
                 style={{
                   width: '100px',
@@ -398,12 +412,7 @@
         </Modal>
       </div>
 
-      <ListItem
-        button
-        alignItems="flex-start"
-        selected={isSelected}
-        onClick={() => navigate(`${navigateUrlPrefix}/${uri}`)}
-      >
+      <ListItem button alignItems="flex-start" selected={isSelected} onClick={() => navigate(uri)}>
         <ListItemAvatar>
           <ConversationAvatar displayName={conversation.getDisplayNameNoFallback()} />
         </ListItemAvatar>
diff --git a/client/src/components/ConversationView.tsx b/client/src/components/ConversationView.tsx
index 4ecffe8..03f0717 100644
--- a/client/src/components/ConversationView.tsx
+++ b/client/src/components/ConversationView.tsx
@@ -21,33 +21,27 @@
 import { useTranslation } from 'react-i18next';
 import { useNavigate } from 'react-router';
 
+import { useAuthContext } from '../contexts/AuthProvider';
 import { SocketContext } from '../contexts/Socket';
 import ChatInterface from '../pages/ChatInterface';
-import { useAccountQuery } from '../services/Account';
 import { useConversationQuery } from '../services/Conversation';
 import { translateEnumeration, TranslateEnumerationOptions } from '../utils/translations';
 import { AddParticipantButton, ShowOptionsMenuButton, StartAudioCallButton, StartVideoCallButton } from './Button';
 import LoadingPage from './Loading';
 
 type ConversationViewProps = {
-  accountId: string;
   conversationId: string;
 };
-const ConversationView = ({ accountId, conversationId }: ConversationViewProps) => {
+const ConversationView = ({ conversationId }: ConversationViewProps) => {
+  const { account } = useAuthContext();
   const socket = useContext(SocketContext);
-  const [account, setAccount] = useState<Account | undefined>();
   const [conversation, setConversation] = useState<Conversation | undefined>();
   const [isLoading, setIsLoading] = useState(true);
   const [error, setError] = useState(false);
 
-  const accountQuery = useAccountQuery(accountId);
-  const conversationQuery = useConversationQuery(accountId, conversationId);
+  const accountId = account.getId();
 
-  useEffect(() => {
-    if (accountQuery.isSuccess) {
-      setAccount(Account.from(accountQuery.data));
-    }
-  }, [accountQuery.isSuccess, accountQuery.data]);
+  const conversationQuery = useConversationQuery(conversationId);
 
   useEffect(() => {
     if (conversationQuery.isSuccess) {
@@ -57,12 +51,12 @@
   }, [accountId, conversationQuery.isSuccess, conversationQuery.data]);
 
   useEffect(() => {
-    setIsLoading(accountQuery.isLoading || conversationQuery.isLoading);
-  }, [accountQuery.isLoading, conversationQuery.isLoading]);
+    setIsLoading(conversationQuery.isLoading);
+  }, [conversationQuery.isLoading]);
 
   useEffect(() => {
-    setError(accountQuery.isError || conversationQuery.isError);
-  }, [accountQuery.isError, conversationQuery.isError]);
+    setError(conversationQuery.isError);
+  }, [conversationQuery.isError]);
 
   useEffect(() => {
     if (!conversation) return;
@@ -94,7 +88,7 @@
           borderTop: '1px solid #E5E5E5',
         }}
       />
-      <ChatInterface account={account} conversationId={conversationId} members={conversation.getMembers()} />
+      <ChatInterface conversationId={conversationId} members={conversation.getMembers()} />
     </Stack>
   );
 };
diff --git a/client/src/components/ConversationsOverviewCard.jsx b/client/src/components/ConversationsOverviewCard.tsx
similarity index 60%
rename from client/src/components/ConversationsOverviewCard.jsx
rename to client/src/components/ConversationsOverviewCard.tsx
index d233df0..4ed0ed4 100644
--- a/client/src/components/ConversationsOverviewCard.jsx
+++ b/client/src/components/ConversationsOverviewCard.tsx
@@ -16,43 +16,46 @@
  * <https://www.gnu.org/licenses/>.
  */
 import { Card, CardActionArea, CardContent, CircularProgress, Typography } from '@mui/material';
-import { Conversation } from 'jami-web-common';
+import { Conversation } from 'jami-web-common/dist/Conversation';
 import { useEffect, useState } from 'react';
-import { useNavigate, useParams } from 'react-router';
+import { useNavigate } from 'react-router';
 
-import authManager from '../AuthManager';
+import { useAuthContext } from '../contexts/AuthProvider';
+import { apiUrl } from '../utils/constants';
 
-export default function ConversationsOverviewCard(props) {
+export default function ConversationsOverviewCard() {
+  const { token, account } = useAuthContext();
   const navigate = useNavigate();
-  let accountId = useParams().accountId;
-  if (props.accountId) {
-    accountId = props.accountId;
-  }
-  const [loaded, setLoaded] = useState(false);
-  const [conversations, setConversations] = useState([]);
+
+  const [conversationCount, setConversationCount] = useState<number | undefined>();
+
+  const accountId = account.getId();
 
   useEffect(() => {
     const controller = new AbortController();
-    authManager
-      .fetch(`/api/accounts/${accountId}/conversations`, { signal: controller.signal })
+    fetch(new URL('/conversations', apiUrl), {
+      headers: {
+        Authorization: `Bearer ${token}`,
+      },
+      signal: controller.signal,
+    })
       .then((res) => res.json())
-      .then((result) => {
+      .then((result: Conversation[]) => {
         console.log(result);
-        setLoaded(true);
-        setConversations(Object.values(result).map((c) => Conversation.from(accountId, c)));
+        setConversationCount(result.length);
       });
-    // return () => controller.abort() // crash on React18
-  }, [accountId]);
+    return () => controller.abort(); // crash on React18
+  }, [token, accountId]);
 
   return (
-    <Card onClick={() => navigate(`/deprecated-account/${accountId}`)}>
+    <Card onClick={() => navigate(`/account`)}>
       <CardActionArea>
         <CardContent>
           <Typography color="textSecondary" gutterBottom>
             Conversations
           </Typography>
           <Typography gutterBottom variant="h5" component="h2">
-            {loaded ? conversations.length : <CircularProgress size={24} />}
+            {conversationCount != null ? conversationCount : <CircularProgress size={24} />}
           </Typography>
         </CardContent>
       </CardActionArea>
diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx
index 445dbae..6276fa4 100644
--- a/client/src/components/Header.tsx
+++ b/client/src/components/Header.tsx
@@ -17,29 +17,19 @@
  */
 import { Box, Button, Menu, MenuItem } from '@mui/material';
 import { MouseEvent, useState } from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
 
 import { useAuthContext } from '../contexts/AuthProvider';
-import { setAccessToken } from '../utils/auth';
 
 export default function Header() {
-  const authContext = useAuthContext(true);
+  const { logout } = useAuthContext();
 
   const navigate = useNavigate();
   const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
   const handleClick = (event: MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget);
   const handleClose = () => setAnchorEl(null);
-  const params = useParams();
 
   const goToContacts = () => navigate(`/contacts`);
-  const goToAccountSettings = () => navigate(`/deprecated-account/${params.accountId}/settings`);
-
-  const deprecatedLogout = () => {
-    setAccessToken('');
-    navigate('/deprecated-account', { replace: true });
-  };
-  // TODO: Remove deprecated_logout once migration to new server is complete
-  const logout = authContext?.logout ?? deprecatedLogout;
 
   return (
     <Box>
@@ -48,7 +38,7 @@
       </Button>
       <Menu id="simple-menu" anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleClose}>
         <MenuItem onClick={goToContacts}>Contacts</MenuItem>
-        {params.accountId && <MenuItem onClick={goToAccountSettings}>Account settings</MenuItem>}
+        <MenuItem onClick={() => navigate('/settings')}>Account settings</MenuItem>
         <MenuItem onClick={logout}>Log out</MenuItem>
       </Menu>
     </Box>
diff --git a/client/src/components/MessageList.tsx b/client/src/components/MessageList.tsx
index 7b9bf2d..0ae04d2 100644
--- a/client/src/components/MessageList.tsx
+++ b/client/src/components/MessageList.tsx
@@ -17,21 +17,22 @@
  */
 import { Typography } from '@mui/material';
 import { Box, Stack } from '@mui/system';
-import { Account, ConversationMember, Message } from 'jami-web-common';
+import { ConversationMember, Message } from 'jami-web-common';
 import { MutableRefObject, useRef, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import { Waypoint } from 'react-waypoint';
 
+import { useAuthContext } from '../contexts/AuthProvider';
 import { MessageRow } from './Message';
 import { ArrowDownIcon } from './SvgIcon';
 
 interface MessageListProps {
-  account: Account;
   members: ConversationMember[];
   messages: Message[];
 }
 
-export default function MessageList({ account, members, messages }: MessageListProps) {
+export default function MessageList({ members, messages }: MessageListProps) {
+  const { account } = useAuthContext();
   const [showScrollButton, setShowScrollButton] = useState(false);
   const listBottomRef = useRef<HTMLElement>();
 
diff --git a/client/src/components/SendMessageForm.tsx b/client/src/components/SendMessageForm.tsx
index 797d810..dfb5f3d 100644
--- a/client/src/components/SendMessageForm.tsx
+++ b/client/src/components/SendMessageForm.tsx
@@ -17,10 +17,11 @@
  */
 import { InputBase } from '@mui/material';
 import { Stack } from '@mui/system';
-import { Account, ConversationMember } from 'jami-web-common';
+import { ConversationMember } from 'jami-web-common';
 import { ChangeEvent, FormEvent, useCallback, useMemo, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 
+import { useAuthContext } from '../contexts/AuthProvider';
 import { translateEnumeration, TranslateEnumerationOptions } from '../utils/translations';
 import {
   RecordVideoMessageButton,
@@ -31,15 +32,14 @@
 } from './Button';
 
 type SendMessageFormProps = {
-  account: Account;
   members: ConversationMember[];
   onSend: (message: string) => void;
   openFilePicker: () => void;
 };
 
-export default function SendMessageForm({ account, members, onSend, openFilePicker }: SendMessageFormProps) {
+export default function SendMessageForm({ members, onSend, openFilePicker }: SendMessageFormProps) {
   const [currentMessage, setCurrentMessage] = useState('');
-  const placeholder = usePlaceholder(account, members);
+  const placeholder = usePlaceholder(members);
 
   const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
     e.preventDefault();
@@ -90,7 +90,8 @@
   );
 }
 
-const usePlaceholder = (account: Account, members: ConversationMember[]) => {
+const usePlaceholder = (members: ConversationMember[]) => {
+  const { account } = useAuthContext();
   const { t } = useTranslation();
 
   return useMemo(() => {
diff --git a/client/src/components/UsernameChooser.jsx b/client/src/components/UsernameChooser.jsx
index 486d947..3c1f584 100644
--- a/client/src/components/UsernameChooser.jsx
+++ b/client/src/components/UsernameChooser.jsx
@@ -20,7 +20,7 @@
 import { useEffect, useState } from 'react';
 import usePromise from 'react-fetch-hook/usePromise';
 
-import authManager from '../AuthManager';
+import { apiUrl } from '../utils/constants.js';
 
 const isInputValid = (input) => input && input.length > 2;
 
@@ -30,7 +30,7 @@
   const { isLoading, data, error } = usePromise(
     () =>
       isInputValid(query)
-        ? authManager.fetch(`/api/ns/name/${query}`).then((res) => {
+        ? fetch(new URL(`/ns/username/${query}`, apiUrl)).then((res) => {
             if (res.status === 200) return res.json();
             else throw res.status;
           })