Integrate new server authentication to client

Changes:
- Use server authentication REST API
- Log in automatically after registration
- Store token in localStorage
- Give feedback to user if registration or login fails

GitLab: #75
Change-Id: Ib90e5b911621567c6825af5e275920d703cdfe88
diff --git a/client/src/components/AccountPreferences.tsx b/client/src/components/AccountPreferences.tsx
index db3e62d..2eedc97 100644
--- a/client/src/components/AccountPreferences.tsx
+++ b/client/src/components/AccountPreferences.tsx
@@ -58,7 +58,6 @@
 
 type AccountPreferencesProps = {
   account: Account;
-  onAccountChanged?: (account: Account) => void;
 };
 
 export default function AccountPreferences({ account }: AccountPreferencesProps) {
diff --git a/client/src/components/AlertSnackbar.tsx b/client/src/components/AlertSnackbar.tsx
new file mode 100644
index 0000000..c32eca8
--- /dev/null
+++ b/client/src/components/AlertSnackbar.tsx
@@ -0,0 +1,46 @@
+/*
+ * 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 { Alert, AlertColor, AlertProps, AlertTitle, Snackbar, SnackbarProps } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+
+type AlertSnackbarProps = AlertProps & {
+  severity: AlertColor;
+  open?: boolean;
+  snackBarProps?: Partial<SnackbarProps>;
+};
+
+export function AlertSnackbar({ severity, open, snackBarProps, children, ...alertProps }: AlertSnackbarProps) {
+  const { t } = useTranslation();
+
+  return (
+    <Snackbar
+      open={open}
+      {...snackBarProps}
+      anchorOrigin={{
+        vertical: 'top',
+        horizontal: 'center',
+        ...snackBarProps?.anchorOrigin,
+      }}
+    >
+      <Alert severity={severity} {...alertProps}>
+        <AlertTitle>{t(`severity_${severity}`)}</AlertTitle>
+        {children}
+      </Alert>
+    </Snackbar>
+  );
+}
diff --git a/client/src/components/ContactList.jsx b/client/src/components/ContactList.jsx
index 2aee98b..56f5f9c 100644
--- a/client/src/components/ContactList.jsx
+++ b/client/src/components/ContactList.jsx
@@ -37,7 +37,7 @@
 };
 
 export default function ContactList() {
-  const { accountId, accountObject } = useAppSelector((state) => state.app);
+  const { accountId, account } = useAppSelector((state) => state.userInfo);
   const dispatch = useAppDispatch();
 
   const [contacts, setContacts] = useState([]);
diff --git a/client/src/components/ConversationList.tsx b/client/src/components/ConversationList.tsx
index 9ad47f6..316792b 100644
--- a/client/src/components/ConversationList.tsx
+++ b/client/src/components/ConversationList.tsx
@@ -31,7 +31,7 @@
   search?: Conversation;
 };
 export default function ConversationList(props: ConversationListProps) {
-  const { refresh } = useAppSelector((state) => state.app);
+  const { refresh } = useAppSelector((state) => state.userInfo);
 
   useEffect(() => {
     console.log('refresh list');
diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx
index 303fc67..f2a0c37 100644
--- a/client/src/components/Header.tsx
+++ b/client/src/components/Header.tsx
@@ -19,7 +19,7 @@
 import { MouseEvent, useState } from 'react';
 import { useNavigate, useParams } from 'react-router-dom';
 
-import authManager from '../AuthManager';
+import { setAccessToken } from '../utils/auth';
 
 export default function Header() {
   const navigate = useNavigate();
@@ -28,20 +28,23 @@
   const handleClose = () => setAnchorEl(null);
   const params = useParams();
 
-  const goToAccountSelection = () => navigate(`/account`);
-  const goToContacts = () => navigate(`/Contacts`);
+  const goToContacts = () => navigate(`/contacts`);
   const goToAccountSettings = () => navigate(`/account/${params.accountId}/settings`);
 
+  const logout = () => {
+    setAccessToken('');
+    navigate('/', { replace: true });
+  };
+
   return (
     <Box>
       <Button aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}>
         Menu
       </Button>
       <Menu id="simple-menu" anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleClose}>
-        <MenuItem onClick={goToAccountSelection}>Change account</MenuItem>
         <MenuItem onClick={goToContacts}>Contacts</MenuItem>
         {params.accountId && <MenuItem onClick={goToAccountSettings}>Account settings</MenuItem>}
-        <MenuItem onClick={() => authManager.disconnect()}>Log out</MenuItem>
+        <MenuItem onClick={logout}>Log out</MenuItem>
       </Menu>
     </Box>
   );
diff --git a/client/src/components/Input.tsx b/client/src/components/Input.tsx
index ae640cf..063b350 100644
--- a/client/src/components/Input.tsx
+++ b/client/src/components/Input.tsx
@@ -53,7 +53,13 @@
   tooltipTitle: string;
 };
 
-export const UsernameInput = ({ infoButtonProps, onChange: _onChange, tooltipTitle, ...props }: InputProps) => {
+export const UsernameInput = ({
+  infoButtonProps,
+  onChange: _onChange,
+  success,
+  tooltipTitle,
+  ...props
+}: InputProps) => {
   const [isSelected, setIsSelected] = useState(false);
   const [input, setInput] = useState(props.defaultValue);
   const [startAdornment, setStartAdornment] = useState<ReactElement | undefined>();
@@ -73,13 +79,13 @@
     let visibility = 'visible';
     if (props.error) {
       Icon = StyledRoundSaltireIconError;
-    } else if (props.success) {
+    } else if (success) {
       Icon = StyledCheckedIconSuccess;
     } else if (!isSelected && !input) {
       visibility = 'hidden'; // keep icon's space so text does not move
     }
     setStartAdornment(<Icon sx={{ visibility }} />);
-  }, [props.error, props.success, isSelected, input]);
+  }, [props.error, success, isSelected, input]);
 
   return (
     <>
@@ -88,7 +94,7 @@
       </RulesDialog>
       <TextField
         {...props}
-        color={inputColor(props.error, props.success)}
+        color={inputColor(props.error, success)}
         label={'Choose an identifier'}
         variant="standard"
         InputLabelProps={{ shrink: !!(isSelected || input) }}
@@ -106,7 +112,13 @@
   );
 };
 
-export const PasswordInput = ({ infoButtonProps, onChange: _onChange, tooltipTitle, ...props }: InputProps) => {
+export const PasswordInput = ({
+  infoButtonProps,
+  onChange: _onChange,
+  success,
+  tooltipTitle,
+  ...props
+}: InputProps) => {
   const [showPassword, setShowPassword] = useState(false);
   const [isSelected, setIsSelected] = useState(false);
   const [input, setInput] = useState(props.defaultValue);
@@ -131,13 +143,13 @@
     let visibility = 'visible';
     if (props.error) {
       Icon = StyledRoundSaltireIconError;
-    } else if (props.success) {
+    } else if (success) {
       Icon = StyledCheckedIconSuccess;
     } else if (!isSelected && !input) {
       visibility = 'hidden'; // keep icon's space so text does not move
     }
     setStartAdornment(<Icon sx={{ visibility }} />);
-  }, [props.error, props.success, isSelected, input]);
+  }, [props.error, success, isSelected, input]);
 
   return (
     <>
@@ -146,7 +158,7 @@
       </RulesDialog>
       <TextField
         {...props}
-        color={inputColor(props.error, props.success)}
+        color={inputColor(props.error, success)}
         label="Password"
         type={showPassword ? 'text' : 'password'}
         variant="standard"