Decouple client from server

Add Vite dependency and remove server side rendering to make it
possible to run the client independently.
Remove webpack config, replace with the `Vite` build tool.

GitLab: #55
Change-Id: I3a05d2e86cf6cb0ab91e77b3696f393132137575
diff --git a/client/src/AuthManager.ts b/client/src/AuthManager.ts
index 29c5ffc..91ac981 100644
--- a/client/src/AuthManager.ts
+++ b/client/src/AuthManager.ts
@@ -61,14 +61,6 @@
 
     this.tasks = [];
     this.onAuthChanged = undefined;
-
-    // @ts-ignore
-    if (initData) {
-      console.log('Using static initData');
-      // @ts-ignore
-      this.setInitData(initData);
-      return;
-    }
   }
 
   isAuthenticated() {
diff --git a/client/src/components/ContactList.js b/client/src/components/ContactList.jsx
similarity index 98%
rename from client/src/components/ContactList.js
rename to client/src/components/ContactList.jsx
index 7f8269b..2aee98b 100644
--- a/client/src/components/ContactList.js
+++ b/client/src/components/ContactList.jsx
@@ -21,8 +21,8 @@
 import { useEffect, useState } from 'react';
 import Modal from 'react-modal';
 
-import { useAppDispatch, useAppSelector } from '../../redux/hooks';
 import authManager from '../AuthManager';
+import { useAppDispatch, useAppSelector } from '../redux/hooks';
 import ConversationAvatar from './ConversationAvatar';
 
 const customStyles = {
diff --git a/client/src/components/ConversationList.tsx b/client/src/components/ConversationList.tsx
index b70001a..9ad47f6 100644
--- a/client/src/components/ConversationList.tsx
+++ b/client/src/components/ConversationList.tsx
@@ -22,7 +22,7 @@
 import { Conversation } from 'jami-web-common';
 import { useEffect } from 'react';
 
-import { useAppSelector } from '../../redux/hooks';
+import { useAppSelector } from '../redux/hooks';
 import ConversationListItem from './ConversationListItem';
 
 type ConversationListProps = {
diff --git a/client/src/components/ConversationListItem.js b/client/src/components/ConversationListItem.jsx
similarity index 98%
rename from client/src/components/ConversationListItem.js
rename to client/src/components/ConversationListItem.jsx
index 0bcb0b3..16c1ccc 100644
--- a/client/src/components/ConversationListItem.js
+++ b/client/src/components/ConversationListItem.jsx
@@ -22,9 +22,9 @@
 import Modal from 'react-modal';
 import { useNavigate, useParams } from 'react-router-dom';
 
-import { setRefreshFromSlice } from '../../redux/appSlice';
-import { useAppDispatch } from '../../redux/hooks';
 import authManager from '../AuthManager';
+import { setRefreshFromSlice } from '../redux/appSlice';
+import { useAppDispatch } from '../redux/hooks';
 import ConversationAvatar from './ConversationAvatar';
 import { RemoveContactIcon, VideoCallIcon } from './SvgIcon.tsx';
 import { AudioCallIcon, BlockContactIcon, ContactDetailsIcon, CrossIcon, MessageIcon } from './SvgIcon.tsx';
diff --git a/client/src/components/ConversationsOverviewCard.js b/client/src/components/ConversationsOverviewCard.jsx
similarity index 100%
rename from client/src/components/ConversationsOverviewCard.js
rename to client/src/components/ConversationsOverviewCard.jsx
diff --git a/client/src/components/ListItemLink.js b/client/src/components/ListItemLink.jsx
similarity index 100%
rename from client/src/components/ListItemLink.js
rename to client/src/components/ListItemLink.jsx
diff --git a/client/src/components/Message.js b/client/src/components/Message.jsx
similarity index 100%
rename from client/src/components/Message.js
rename to client/src/components/Message.jsx
diff --git a/client/src/components/MessageList.js b/client/src/components/MessageList.jsx
similarity index 100%
rename from client/src/components/MessageList.js
rename to client/src/components/MessageList.jsx
diff --git a/client/src/components/UsernameChooser.js b/client/src/components/UsernameChooser.jsx
similarity index 100%
rename from client/src/components/UsernameChooser.js
rename to client/src/components/UsernameChooser.jsx
diff --git a/client/src/components/welcome.js b/client/src/components/welcome.jsx
similarity index 90%
rename from client/src/components/welcome.js
rename to client/src/components/welcome.jsx
index ca160a0..39e222b 100644
--- a/client/src/components/welcome.js
+++ b/client/src/components/welcome.jsx
@@ -19,8 +19,6 @@
 import { AnimatePresence, motion } from 'framer-motion';
 import { useState } from 'react';
 
-import JamiLogo from '../../public/jami-logo-icon.svg';
-
 const list = {
   hidden: { opacity: 0 },
   visible: {
@@ -57,7 +55,14 @@
             }}
           >
             <motion.div variants={item}>
-              <JamiLogo size="32px" />
+              <img
+                src="/jami-logo-icon.svg"
+                style={{
+                  width: '32',
+                  height: '32',
+                }}
+                alt="jami n/logo"
+              />
             </motion.div>
             <motion.h1 variants={item}>Welcome to Jami</motion.h1>
             {props.showSetup && (
diff --git a/client/src/i18n.ts b/client/src/i18n.ts
index 63d7e1c..37f9f7f 100644
--- a/client/src/i18n.ts
+++ b/client/src/i18n.ts
@@ -18,11 +18,11 @@
 import i18n from 'i18next';
 import { initReactI18next } from 'react-i18next';
 
-import translationEn from '../public/locale/en/translation.json';
-import translationFr from '../public/locale/fr/translation.json';
+import translationEn from './locale/en/translation.json';
+import translationFr from './locale/fr/translation.json';
 
 i18n.use(initReactI18next).init({
-  debug: process.env.NODE_ENV == 'development',
+  debug: import.meta.env.DEV,
   lng: 'en',
   interpolation: {
     escapeValue: false,
diff --git a/client/src/index.ejs b/client/src/index.ejs
deleted file mode 100644
index d68639a..0000000
--- a/client/src/index.ejs
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="utf-8" />
-  <link rel="icon" href="/favicon.png" />
-  <link rel="apple-touch-icon" href="/logo192.png" />
-  <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
-  <meta name="description" content="Jami Web" />
-  <script>const initData=<%- initdata %></script>
-  <title>Jami Web</title>
-</head>
-
-<body>
-  <div id="root"></div>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/client/src/index.tsx b/client/src/index.tsx
index fdd2fe7..83c2ada 100644
--- a/client/src/index.tsx
+++ b/client/src/index.tsx
@@ -15,7 +15,6 @@
  * License along with this program.  If not, see
  * <https://www.gnu.org/licenses/>.
  */
-/// <reference types="webpack/module" />
 'use strict';
 import './index.scss';
 import './i18n';
@@ -23,15 +22,14 @@
 // import config from "../sentry-client.config.json"
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 import { StrictMode } from 'react';
-import { render } from 'react-dom';
 import { createRoot } from 'react-dom/client';
 import { Provider } from 'react-redux';
 import { BrowserRouter as Router } from 'react-router-dom';
 import socketio from 'socket.io-client';
 
-import { store } from '../redux/store';
 import App from './App';
 import { SocketProvider } from './contexts/Socket';
+import { store } from './redux/store';
 
 const queryClient = new QueryClient({
   defaultOptions: {
@@ -61,12 +59,3 @@
     </StrictMode>
   </Provider>
 );
-
-if (import.meta.webpackHot)
-  import.meta.webpackHot.accept('./App', () => {
-    try {
-      render(<App />, container);
-    } catch (e) {
-      location.reload();
-    }
-  });
diff --git a/client/src/locale/en/translation.json b/client/src/locale/en/translation.json
new file mode 100644
index 0000000..19ddf26
--- /dev/null
+++ b/client/src/locale/en/translation.json
@@ -0,0 +1,4 @@
+{
+  "message_swarm_created": "Swarm created",
+  "message_user_joined": "{{user}} joined"
+}
diff --git a/client/src/locale/fr/translation.json b/client/src/locale/fr/translation.json
new file mode 100644
index 0000000..332c3d4
--- /dev/null
+++ b/client/src/locale/fr/translation.json
@@ -0,0 +1,4 @@
+{
+  "message_swarm_created": "Le Swarm a été créé",
+  "message_user_joined": "{{user}} s'est joint"
+}
diff --git a/client/src/pages/AccountSettings.tsx b/client/src/pages/AccountSettings.tsx
index 35c0678..fe98277 100644
--- a/client/src/pages/AccountSettings.tsx
+++ b/client/src/pages/AccountSettings.tsx
@@ -20,11 +20,11 @@
 import { useEffect, useState } from 'react';
 import { useParams } from 'react-router';
 
-import { setAccountId, setAccountObject } from '../../redux/appSlice';
-import { useAppDispatch } from '../../redux/hooks';
 import authManager from '../AuthManager';
 import AccountPreferences from '../components/AccountPreferences';
 import Header from '../components/Header';
+import { setAccountId, setAccountObject } from '../redux/appSlice';
+import { useAppDispatch } from '../redux/hooks';
 
 type AccountSettingsProps = {
   accountId?: string;
diff --git a/client/src/pages/AddContactPage.tsx b/client/src/pages/AddContactPage.tsx
index d9c8394..666625c 100644
--- a/client/src/pages/AddContactPage.tsx
+++ b/client/src/pages/AddContactPage.tsx
@@ -19,9 +19,9 @@
 import { Box, Card, CardContent, Container, Fab, Typography } from '@mui/material';
 import { useNavigate, useParams } from 'react-router-dom';
 
-import { setRefreshFromSlice } from '../../redux/appSlice';
-import { useAppDispatch } from '../../redux/hooks';
 import authManager from '../AuthManager';
+import { setRefreshFromSlice } from '../redux/appSlice';
+import { useAppDispatch } from '../redux/hooks';
 
 type AddContactPageProps = {
   accountId: string;
diff --git a/client/src/pages/Messenger.tsx b/client/src/pages/Messenger.tsx
index 1fd2252..6aeb840 100644
--- a/client/src/pages/Messenger.tsx
+++ b/client/src/pages/Messenger.tsx
@@ -20,7 +20,6 @@
 import { useEffect, useState } from 'react';
 import { useParams } from 'react-router';
 
-import { useAppSelector } from '../../redux/hooks';
 import authManager from '../AuthManager';
 //import Sound from 'react-sound';
 import ConversationList from '../components/ConversationList';
@@ -28,6 +27,7 @@
 import Header from '../components/Header';
 import LoadingPage from '../components/Loading';
 import NewContactForm from '../components/NewContactForm';
+import { useAppSelector } from '../redux/hooks';
 import AddContactPage from './AddContactPage';
 
 type MessengerProps = {
diff --git a/client/src/redux/appSlice.ts b/client/src/redux/appSlice.ts
new file mode 100644
index 0000000..d5c01ee
--- /dev/null
+++ b/client/src/redux/appSlice.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { Account } from 'jami-web-common';
+
+// Define a type for the slice state
+export interface appState {
+  accountId: string;
+  accountObject: Account | null;
+  refresh: boolean;
+}
+
+// Define the initial state using that type
+const initialState: appState = {
+  accountId: '',
+  accountObject: null,
+  refresh: true,
+};
+
+export const appSlice = createSlice({
+  name: 'app',
+  // `createSlice` will infer the state type from the `initialState` argument
+  initialState,
+  reducers: {
+    setAccountId: (state, action: PayloadAction<string>) => {
+      state.accountId = action.payload;
+    },
+    setAccountObject: (state, action: PayloadAction<Account>) => {
+      state.accountObject = action.payload;
+    },
+    setRefreshFromSlice: (state) => {
+      state.refresh = !state.refresh;
+    },
+  },
+});
+
+export const { setAccountId, setAccountObject, setRefreshFromSlice } = appSlice.actions;
+
+// Other code such as selectors can use the imported `RootState` type
+// export const selectCount = (state: RootState) => state.app.value;
+
+export default appSlice.reducer;
diff --git a/client/src/redux/hooks.ts b/client/src/redux/hooks.ts
new file mode 100644
index 0000000..146b3b1
--- /dev/null
+++ b/client/src/redux/hooks.ts
@@ -0,0 +1,24 @@
+/*
+ * 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 { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
+
+import type { AppDispatch, RootState } from './store';
+
+// Use throughout your app instead of plain `useDispatch` and `useSelector`
+export const useAppDispatch: () => AppDispatch = useDispatch;
+export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
diff --git a/client/src/redux/store.ts b/client/src/redux/store.ts
new file mode 100644
index 0000000..be2b4a5
--- /dev/null
+++ b/client/src/redux/store.ts
@@ -0,0 +1,31 @@
+/*
+ * 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 { configureStore } from '@reduxjs/toolkit';
+
+import appReducer from './appSlice';
+
+export const store = configureStore({
+  reducer: {
+    app: appReducer,
+  },
+});
+
+// Infer the `RootState` and `AppDispatch` types from the store itself
+export type RootState = ReturnType<typeof store.getState>;
+// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
+export type AppDispatch = typeof store.dispatch;