Add pending and pending receiving user interfaces

GitLab: #101
GitLab: #102

Change-Id: I202372a92246c72225bb1fe277147fe5ee2f9981
diff --git a/client/src/components/CallButtons.tsx b/client/src/components/CallButtons.tsx
index 2392c00..79d5485 100644
--- a/client/src/components/CallButtons.tsx
+++ b/client/src/components/CallButtons.tsx
@@ -32,7 +32,9 @@
   MicroIcon,
   MicroOffIcon,
   MoreVerticalIcon,
+  PlaceAudioCallIcon,
   RecordingIcon,
+  RoundCloseIcon,
   ScreenShareArrowIcon,
   ScreenShareRegularIcon,
   ScreenShareScreenAreaIcon,
@@ -42,23 +44,33 @@
   WindowIcon,
 } from './SvgIcon';
 
+type ColoredCallButtonColor = 'red' | 'green';
+
 const CallButton = styled((props: ExpandableButtonProps) => {
   return <ExpandableButton {...props} />;
 })({
+  color: 'white',
   '&:hover': {
     backgroundColor: 'rgba(255, 255, 255, 0.15)',
   },
-  color: 'white',
 });
 
-const ColoredCallButton = styled((props: ExpandableButtonProps) => {
-  return <ExpandableButton {...props} />;
-})({
-  color: 'white',
-  backgroundColor: '#a30000',
-  '&:hover': {
-    backgroundColor: '#ff0000',
-  },
+const ColoredCallButton = styled(
+  ({
+    ...props
+  }: ExpandableButtonProps & {
+    buttonColor: ColoredCallButtonColor;
+  }) => {
+    return <ExpandableButton {...props} />;
+  }
+)(({ buttonColor }) => {
+  return {
+    color: 'white',
+    backgroundColor: buttonColor === 'green' ? '#183722' : '#5E070D',
+    '&:hover': {
+      backgroundColor: buttonColor === 'green' ? '#0B8271' : '#CC0022',
+    },
+  };
 });
 
 export const CallingChatButton = (props: ExpandableButtonProps) => {
@@ -66,7 +78,7 @@
 };
 
 export const CallingEndButton = (props: ExpandableButtonProps) => {
-  return <ColoredCallButton sx={{}} aria-label="call end" Icon={CallEndIcon} {...props} />;
+  return <ColoredCallButton buttonColor="red" aria-label="call end" Icon={CallEndIcon} {...props} />;
 };
 
 export const CallingExtensionButton = (props: ExpandableButtonProps) => {
@@ -159,7 +171,6 @@
 
 export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
   const { isVideoOn, setVideoStatus } = useContext(WebRTCContext);
-
   return (
     <CallButton
       aria-label="camera options"
@@ -176,3 +187,16 @@
     />
   );
 };
+
+// Calling pending/receiving interface
+export const CallingAnswerAudioButton = (props: ExpandableButtonProps) => {
+  return <ColoredCallButton aria-label="answer audio" buttonColor="green" Icon={PlaceAudioCallIcon} {...props} />;
+};
+
+export const CallingAnswerVideoButton = (props: ExpandableButtonProps) => {
+  return <ColoredCallButton aria-label="answer video" buttonColor="green" Icon={VideoCameraIcon} {...props} />;
+};
+
+export const CallingRefuseButton = (props: ExpandableButtonProps) => {
+  return <ColoredCallButton aria-label="reject" buttonColor="red" Icon={RoundCloseIcon} {...props} />;
+};
diff --git a/client/src/components/ConversationListItem.tsx b/client/src/components/ConversationListItem.tsx
index 9fa1acc..91dd0f1 100644
--- a/client/src/components/ConversationListItem.tsx
+++ b/client/src/components/ConversationListItem.tsx
@@ -147,7 +147,7 @@
     closeModalDelete();
   };
 
-  const uri = conversation.getId() ? `/conversation/${conversation.getId()}` : `/addContact/${userId}`;
+  const uri = conversation.getId() ? `/conversation/${conversation.getId()}` : `/add-contact/${userId}`;
   return (
     <div onContextMenu={openMenu}>
       <div>
diff --git a/client/src/components/SvgIcon.tsx b/client/src/components/SvgIcon.tsx
index 88423b1..8f83587 100644
--- a/client/src/components/SvgIcon.tsx
+++ b/client/src/components/SvgIcon.tsx
@@ -552,6 +552,23 @@
   );
 };
 
+export const PlaceAudioCallIcon = (props: SvgIconProps) => {
+  return (
+    <SvgIcon {...props} viewBox="0 0 24 24">
+      <g id="Icons_Outline" stroke="#ffffff" strokeWidth="1.75" fill="none" fillRule="evenodd">
+        <g id="Phone">
+          <g id="Ico_TEL" transform="translate(3.000000, 3.000000)">
+            <path
+              d="M2.08318114,0.702801822 C3.47221232,-0.375546383 4.67183016,-0.185249641 5.24007015,1.08339531 C6.37655021,3.36695621 7.19734136,4.50873667 6.37655021,5.39678812 C4.86124348,6.41170409 3.78790122,6.91916206 4.35614125,7.99751029 C5.49262131,10.7250969 7.70244359,13.0720901 10.3542304,14.4675995 C11.3644349,15.101922 11.9326749,14.0235738 13.0691549,12.6280643 C14.0162216,11.8668773 15.0895639,12.6914966 17.2993862,14.0235738 C18.4990041,14.7213285 18.5621418,15.8631089 17.4256618,17.1951861 C10.7961949,24.6801913 -5.80904142,6.85572983 2.08318114,0.702801822"
+              id="Path"
+            ></path>
+          </g>
+        </g>
+      </g>
+    </SvgIcon>
+  );
+};
+
 export const RecordingIcon = (props: SvgIconProps) => {
   return (
     <SvgIcon {...props} viewBox="0 0 24 24">
@@ -588,6 +605,15 @@
   );
 };
 
+export const RoundCloseIcon = (props: SvgIconProps) => {
+  return (
+    <SvgIcon {...props} viewBox="0 0 24 24">
+      <path fill="none" stroke="none" d="M0 0h24v24H0V0z" />
+      <path d="M18.3 5.71c-.39-.39-1.02-.39-1.41 0L12 10.59 7.11 5.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L10.59 12 5.7 16.89c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 13.41l4.89 4.89c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z" />
+    </SvgIcon>
+  );
+};
+
 export const RoundCrossIcon = (props: SvgIconProps) => {
   return (
     <SvgIcon {...props} viewBox="0 0 16 16">
@@ -763,7 +789,7 @@
 export const VideoCameraOffIcon = (props: SvgIconProps) => {
   return (
     <SvgIcon {...props} viewBox="0 0 24 24">
-      <g id="Icones_Outline" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
+      <g id="Icons_Outline" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
         <g id="Camera-Off" stroke="#ffffff" strokeWidth="1.5">
           <line x1="4.5" y1="18.5" x2="15.5" y2="6.5" id="Line" strokeLinecap="round"></line>
           <g id="Ico_Camera" transform="translate(3.000000, 5.000000)" fillRule="nonzero" strokeLinejoin="round">
diff --git a/client/src/locale/en/translation.json b/client/src/locale/en/translation.json
index bd21509..e797090 100644
--- a/client/src/locale/en/translation.json
+++ b/client/src/locale/en/translation.json
@@ -1,7 +1,7 @@
 {
   "severity": "",
   "share_screen": "Share your screen",
-  "share_window": "",
+  "share_window": "Share window",
   "share_screen_area": "Share an area of your screen",
   "share_file": "Share a file",
   "dummy_option_string": "Test option",
@@ -54,8 +54,12 @@
   "message_input_placeholder_two": "Write to {{member0}} and {{member1}}",
   "message_input_placeholder_three": "Write to {{member0}}, {{member1}} and {{member2}}",
   "message_input_placeholder_four": "Write to {{member0}}, {{member1}}, {{member2}}, +1 other member",
-  "message_input_placeholder_more": "Write to {{member0}}, {{member1}}, {{member2}}, +{{excess}} other members",
-  "conversation_add_contact": "Add contact",
+  "message_input_placeholder_more": "Write to {{member01}}, {{member1}}, {{member2}}, +{{excess}} other members",
+  "conversation_add_contact": "",
+  "calling": "Calling...",
+  "connecting": "Connecting...",
+  "incoming_call_{medium}": "",
+  "end_call": "End call",
   "login_username_not_found": "Username not found",
   "login_invalid_password": "Incorrect password",
   "login_form_title": "LOGIN",
@@ -71,14 +75,14 @@
   "registration_form_submit_button": "REGISTER",
   "registration_form_to_login_text": "Already have an account?",
   "registration_form_to_login_link": "LOG IN",
-  "logout": "LOGOUT",
-  "setup_login_title": "Jami web node setup",
-  "setup_login_welcome": "Welcome to the Jami web node setup.",
-  "setup_login_admin_creation": "Let's start by creating a new administrator account to control access to the server configuration.",
-  "password_placeholder": "Password",
-  "setup_login_password_placeholder_creation": "New password",
-  "setup_login_password_placeholder_repeat": "Repeat password",
-  "admin_creation_submit_button": "CREATE ADMIN ACCOUNT",
+  "logout": "",
+  "setup_login_title": "",
+  "setup_login_welcome": "",
+  "setup_login_admin_creation": "",
+  "password_placeholder": "",
+  "setup_login_password_placeholder_creation": "",
+  "setup_login_password_placeholder_repeat": "",
+  "admin_creation_submit_button": "",
   "severity_error": "Error",
   "severity_success": "Success"
 }
diff --git a/client/src/locale/fr/translation.json b/client/src/locale/fr/translation.json
index e61c57a..90818e5 100644
--- a/client/src/locale/fr/translation.json
+++ b/client/src/locale/fr/translation.json
@@ -1,7 +1,7 @@
 {
   "severity": "",
   "share_screen": "Partager votre écran",
-  "share_window": "",
+  "share_window": "Partager la fenêtre",
   "share_screen_area": "Partager une partie de l'écran",
   "share_file": "Partager le fichier",
   "dummy_option_string": "Option test",
@@ -55,7 +55,11 @@
   "message_input_placeholder_three": "Écrire à {{member0}}, {{member1}} et {{member2}}",
   "message_input_placeholder_four": "Écrire à {{member0}}, {{member1}}, {{member2}}, +1 autre membre",
   "message_input_placeholder_more": "Écrire à {{member01}}, {{member1}}, {{member2}}, +{{excess}} autres membres",
-  "conversation_add_contact": "Ajouter le contact",
+  "conversation_add_contact": "",
+  "calling": "Appel en cours...",
+  "connecting": "Connexion en cours...",
+  "incoming_call_{medium}": "",
+  "end_call": "Fin d'appel",
   "login_username_not_found": "Nom d'utilisateur introuvable",
   "login_invalid_password": "Mot de passe incorrect",
   "login_form_title": "CONNEXION",
@@ -71,14 +75,14 @@
   "registration_form_submit_button": "S'INSCRIRE",
   "registration_form_to_login_text": "Déjà inscrit?",
   "registration_form_to_login_link": "SE CONNECTER",
-  "logout": "SE DÉCONNECTER",
-  "setup_login_title": "Configuration du noeud web Jami",
-  "setup_login_welcome": "Bienvenue à la configuration du noeud web Jami.",
-  "setup_login_admin_creation": "Commençons par créer un nouveau compte administrateur pour contrôler l'accès à la configuration du serveur.",
-  "password_placeholder": "Mot de passe",
-  "setup_login_password_placeholder_creation": "Nouveau mot de passe",
-  "setup_login_password_placeholder_repeat": "Répéter le mot de passe",
-  "admin_creation_submit_button": "CRÉER UN COMPTE ADMIN",
+  "logout": "",
+  "setup_login_title": "",
+  "setup_login_welcome": "",
+  "setup_login_admin_creation": "",
+  "password_placeholder": "",
+  "setup_login_password_placeholder_creation": "",
+  "setup_login_password_placeholder_repeat": "",
+  "admin_creation_submit_button": "",
   "severity_error": "Erreur",
   "severity_success": "Succès"
 }
diff --git a/client/src/pages/CallPending.tsx b/client/src/pages/CallPending.tsx
new file mode 100644
index 0000000..27d28e5
--- /dev/null
+++ b/client/src/pages/CallPending.tsx
@@ -0,0 +1,171 @@
+/*
+ * 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, CircularProgress, Grid, Stack, Typography } from '@mui/material';
+import { Trans } from 'react-i18next';
+
+import {
+  CallingAnswerAudioButton,
+  CallingAnswerVideoButton,
+  CallingEndButton,
+  CallingRefuseButton,
+} from '../components/CallButtons';
+
+export type CallPendingProps = {
+  pending: PendingStatus;
+  caller?: CallerStatus;
+  medium?: CommunicationMedium;
+};
+
+type PendingStatus = 'caller' | 'receiver';
+type CallerStatus = 'calling' | 'connecting';
+type CommunicationMedium = 'audio' | 'video';
+
+const RECEIVER_BUTTONS = [
+  {
+    ButtonComponent: CallingRefuseButton,
+    translationKey: 'refuse_call',
+  },
+  {
+    ButtonComponent: CallingAnswerAudioButton,
+    translationKey: 'accept_call_audio',
+  },
+  {
+    ButtonComponent: CallingAnswerVideoButton,
+    translationKey: 'accept_call_video',
+  },
+];
+
+export const CallPending = (props: CallPendingProps) => {
+  return (
+    <Stack
+      direction="column"
+      justifyContent="center"
+      alignItems="center"
+      height="100%"
+      spacing={4}
+      sx={{
+        backgroundColor: 'black',
+      }}
+    >
+      <Box
+        sx={{
+          position: 'relative',
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+          width: '100%',
+          height: '30%',
+        }}
+      >
+        <Box
+          sx={{
+            aspectRatio: '1',
+            height: '100%',
+            position: 'absolute',
+          }}
+        >
+          <CircularProgress
+            disableShrink
+            thickness={1}
+            size="100%"
+            sx={{
+              position: 'absolute',
+              color: 'white',
+              zIndex: 1,
+            }}
+          />
+          <img
+            // TODO: Insert incoming caller icon here
+            style={{
+              position: 'absolute',
+              objectFit: 'cover',
+              width: '100%',
+              height: '100%',
+              maxWidth: '100%',
+              borderRadius: '50%',
+              aspectRatio: '1',
+            }}
+          />
+        </Box>
+      </Box>
+      {props.pending === 'caller' ? (
+        <CallPendingCallerInterface {...props} />
+      ) : (
+        <CallPendingReceiverInterface {...props} />
+      )}
+    </Stack>
+  );
+};
+
+export const CallPendingCallerInterface = ({ caller }: CallPendingProps) => {
+  // TODO: Remove the dummy name
+  const defaultName = 'Alex Thérieur';
+  return (
+    <Stack textAlign="center" spacing={2}>
+      <Typography variant="h1" color="white">
+        {defaultName}
+      </Typography>
+      <Typography variant="h3" color="white">
+        {caller === 'calling' ? <Trans i18nKey="calling" /> : <Trans i18nKey="connecting" />}
+      </Typography>
+      <CallerButtons />
+    </Stack>
+  );
+};
+
+export const CallPendingReceiverInterface = ({ medium }: CallPendingProps) => {
+  // TODO: Remove the dummy name
+  const defaultName = 'Alain Thérieur';
+  return (
+    <Stack textAlign="center" spacing={2}>
+      <Typography variant="h1" color="white">
+        <Trans i18nKey="incoming_call" context={medium} values={{ member0: defaultName }} />
+      </Typography>
+      <ReceiverButtons />
+    </Stack>
+  );
+};
+
+const CallerButtons = () => {
+  return (
+    <Stack textAlign="center" spacing={1}>
+      <CallingEndButton size="large" />
+      <Typography variant="body2" color="white">
+        <Trans i18nKey="end_call" />
+      </Typography>
+    </Stack>
+  );
+};
+
+const ReceiverButtons = () => {
+  return (
+    <Grid container direction="row" padding={2}>
+      {RECEIVER_BUTTONS.map(({ ButtonComponent, translationKey }, i) => (
+        <Grid item xs={4} key={i}>
+          <Stack spacing={1}>
+            <ButtonComponent color="inherit" size="large" />
+            <Typography variant="body2" color="white" textAlign="center" sx={{ opacity: 0.75 }}>
+              <Trans i18nKey={'' + translationKey} />
+            </Typography>
+          </Stack>
+        </Grid>
+      ))}
+    </Grid>
+  );
+};
diff --git a/client/src/router.tsx b/client/src/router.tsx
index f378a37..b372099 100644
--- a/client/src/router.tsx
+++ b/client/src/router.tsx
@@ -49,7 +49,7 @@
           </AuthProvider>
         }
       >
-        <Route path="addContact/:contactId" element={<Messenger />} />
+        <Route path="add-contact/:contactId" element={<Messenger />} />
         <Route path="conversation/:conversationId" element={<Messenger />} />
         <Route path="call/:conversationId" element={<CallInterface />} />
         <Route path="settings" element={<AccountSettings />} />