use ESM, add server setup, cleanup

Change-Id: Iafac35c2082523ae98c31017d9bad5c4d6e18ef3
diff --git a/JamiDaemon.js b/JamiDaemon.js
index 404915b..841b979 100755
--- a/JamiDaemon.js
+++ b/JamiDaemon.js
@@ -17,10 +17,12 @@
  *  You should have received a copy of the GNU General Public License
  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
-"use strict";
+"use strict"
 
-const Account = require('./model/Account')
-const Conversation = require('./model/Conversation')
+import Account from './model/Account.js'
+import Conversation from './model/Conversation.js'
+import { createRequire } from 'module';
+const require = createRequire(import.meta.url);
 
 class JamiDaemon {
     constructor() {
@@ -66,13 +68,13 @@
                 console.log(`Received message: ${accountId} ${from} ${message["text/plain"]}`)
 /*
                 if (parser.validate(message["text/plain"]) === true) {
-                    console.log(message["text/plain"]);
+                    console.log(message["text/plain"])
                 } else {
 
-                    user = connectedUsers[accountId];
+                    user = connectedUsers[accountId]
                     console.log(user.socketId)
-                    io.to(user.socketId).emit('receivedMessage', message["text/plain"]);
-                    //io.emit('receivedMessage', message["text/plain"]);
+                    io.to(user.socketId).emit('receivedMessage', message["text/plain"])
+                    //io.emit('receivedMessage', message["text/plain"])
                 }*/
             },
             "RegistrationStateChanged": (accountId, state, /*int*/ code, detail) => {
@@ -109,14 +111,14 @@
                     const contact = account.getContactFromCache(address)
                     contact.setRegisteredName(name)
                 }
-                let index = account.lookups.length - 1;
+                let index = account.lookups.length - 1
                 while (index >= 0) {
                     const lookup = account.lookups[index]
                     if ((lookup.address && lookup.address === address) || (lookup.name && lookup.name === name)) {
                         lookup.resolve({address, name, state})
-                        account.lookups.splice(index, 1);
+                        account.lookups.splice(index, 1)
                     }
-                    index -= 1;
+                    index -= 1
                 }
             },
             "ConversationReady": (accountId, conversationId) => {
@@ -303,7 +305,7 @@
             params.set("Account.archivePassword", account.archivePassword)
         } else {
             console.log("archivePassword required")
-            return;
+            return
         }
         if (account.alias)
             params.set("Account.alias", account.alias)
@@ -371,8 +373,8 @@
     mapToNative(map){
         const ret = new this.dring.StringMap()
         map.forEach((value, key) => ret.set(key, value))
-        return ret;
+        return ret
     }
 }
 
-module.exports = JamiDaemon;
+export default JamiDaemon
diff --git a/app.js b/app.js
index 0c92661..ef03a00 100644
--- a/app.js
+++ b/app.js
@@ -1,46 +1,63 @@
-require('dotenv').config()
+'use strict'
 
-const express = require('express')
-const http = require('http')
-const session = require('express-session')
+import dotenv from 'dotenv'
+dotenv.config()
+
+import { promises as fs } from 'fs'
+import http from 'http'
+import express from 'express'
+import session from 'express-session'
 //const cookieParser = require('cookie-parser')
 //const io = require('socket.io')(server)
-const path = require('path')
-const passport = require('passport')
-    , LocalStrategy = require('passport-local').Strategy
+import path from 'path'
+import passport from 'passport'
+import { Strategy as LocalStrategy } from 'passport-local'
+//import { createRequire } from 'module';
+//const require = createRequire(import.meta.url);
+import { fileURLToPath } from 'url';
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
 
 //const redis = require('redis-url').connect()
 //const RedisStore = require('connect-redis')(session)
 /*const passportSocketIo = require('passport.socketio')*/
 
-const indexRouter = require('./routes/index')
+import indexRouter from './routes/index.js'
 
 //const cors = require('cors')
 
-const JamiRestApi = require('./routes/jami')
-const JamiDaemon = require('./JamiDaemon')
+import JamiRestApi from './routes/jami.js'
+import JamiDaemon from './JamiDaemon.js'
+
+const configPath = 'jamiServerConfig.json'
 
 //const sessionStore = new RedisStore({ client: redis })
 const sessionStore = new session.MemoryStore()
 
 const loadConfig = async (filePath) => {
+    const config = {users: {}, authMethods: []}
     try {
-        return JSON.parse(await fs.readFile(filePath))
-    } catch {
-        return {}
+        return Object.assign(config, JSON.parse(await fs.readFile(filePath)))
+    } catch(e) {
+        console.log(e)
+        return config
     }
 }
 
+const saveConfig = (filePath, config) => {
+    return fs.writeFile(filePath, JSON.stringify(config))
+}
+
 /*
 Share sessions between Passport.js and Socket.io
 */
 
 function logSuccess() {
-    console.log("passportSocketIo authorized user with Success 😁");
+    console.log("passportSocketIo authorized user with Success 😁")
 }
 
 function logFail() {
-    console.log("passportSocketIo failed to authorized user 👺");
+    console.log("passportSocketIo failed to authorized user 👺")
 }
 
 /*
@@ -52,8 +69,8 @@
 web socket call
 
 */
-const tempAccounts = {};
-const connectedUsers = {};
+const tempAccounts = {}
+const connectedUsers = {}
 
 const createServer = async (appConfig) => {
     const app = express()
@@ -62,19 +79,23 @@
     const development = app.get('env') === 'development'
 
     if (development) {
-        const webpack = require('webpack')
-        const webpackConfig = require('./client/webpack.config.js')
-        const compiler = webpack(webpackConfig)
-        app.use(require('webpack-dev-middleware')(compiler, {
-            publicPath: webpackConfig.output.publicPath
-        }));
-        app.use(require('webpack-hot-middleware')(compiler));
+        const [ webpack, webpackDev, webpackHot, webpackConfig ] = await Promise.all([
+            import('webpack'),
+            import('webpack-dev-middleware'),
+            import('webpack-hot-middleware'),
+            import('./client/webpack.config.js')
+        ])
+        const compiler = webpack.default(webpackConfig.default)
+        app.use(webpackDev.default(compiler, {
+            publicPath: webpackConfig.default.output.publicPath
+        }))
+        app.use(webpackHot.default(compiler))
     }
 
     /*
         Configuation for Passeport Js
     */
-    app.disable('x-powered-by');
+    app.disable('x-powered-by')
 
     app.use(session({
         store: sessionStore,
@@ -85,13 +106,13 @@
             maxAge: 2419200000
         },
         secret: process.env.SECRET_KEY_BASE
-    }));
-    app.use(passport.initialize());
-    app.use(passport.session());
-    // app.use(app.router);
+    }))
+    app.use(passport.initialize())
+    app.use(passport.session())
+    // app.use(app.router)
     //app.use(cors())
 
-    const jami = new JamiDaemon();
+    const jami = new JamiDaemon()
     const apiRouter = new JamiRestApi(jami).getRouter()
 
     /*
@@ -103,166 +124,204 @@
         cookieParser: cookieParser,
         //success: logSuccess(),
         // fail: logFail(),
-    }));
+    }))
     */
 
+    const isSetupComplete = () => {
+        return 'admin' in appConfig.users
+    }
+
+    const accountFilter = filter => {
+        if (typeof filter === 'string') {
+            if (filter === '*')
+                return undefined
+            else
+                return account => account.getId() === filter
+        } else if (Array.isArray(filter)) {
+            return account => filter.includes(account.getId())
+        } else {
+            throw new Error('Invalid account filter string')
+        }
+    }
+
+    const user = (id, config) => {
+        return {
+            id,
+            config,
+            username: config.username || id,
+            accountFilter: accountFilter(config.accounts)
+        }
+    }
+
     passport.serializeUser((user, done) => {
+        connectedUsers[user.id] = user.config
+        console.log("=============================SerializeUser called " + user.id)
         console.log(user)
-        connectedUsers[user.accountId] = user;
-        console.log("=============================SerializeUser called " + user.accountId)
-        done(null, user.accountId);
-    });
+        done(null, user.id)
+    })
 
     const deserializeUser = (id, done) => {
-        console.log("=============================DeserializeUser called on: " + id + " " + connectedUsers[id])
-        done(null, connectedUsers[id]);
-    };
-    passport.deserializeUser(deserializeUser);
-
-    //var tempAccountId = '';
+        console.log("=============================DeserializeUser called on: " + id)
+        const userConfig = connectedUsers[id]
+        console.log(userConfig)
+        if (userConfig) {
+            done(null, user(id, userConfig))
+        } else
+            done(404, null)
+    }
+    passport.deserializeUser(deserializeUser)
 
     const jamsStrategy = new LocalStrategy(
         (username, password, done) => {
-
-            const newUser = {};
-            newUser.username = username;
-            //newUser.socketid =
-
             const accountId = jami.addAccount({
                 'managerUri': 'https://jams.savoirfairelinux.com',
                 'managerUsername': username,
                 'archivePassword': password
-            });
+            })
+            const id = `jams_${username}`
+            const userConfig = { username, type: 'jams', accounts: accountId }
+            const newUser = user(id, userConfig)
+            console.log("AccountId: " + accountId)
+            tempAccounts[accountId] = { done, newUser }
 
-            const newProps = jami.getAccount(accountId).details;
-            console.log(newProps);
-            //Object.entries(newProps).forEach(v => console.log(v[0], v[1]))
-            //tempAccountId = accountId;
-            newUser.accountId = accountId;
-            console.log("AccountId: " + accountId);
-            connectedUsers[accountId] = newUser;
-            tempAccounts[accountId] = { done, newUser };
-
-            //return done(null, newUser);
-
-            /*User.findOne({ username: username }, function (err, user) {
-                if (err) { return done(err); }
-                if (!user) {
-                    return done(null, false, { message: 'Incorrect username.' });
-                }
-                if (!user.validPassword(password)) {
-                    return done(null, false, { message: 'Incorrect password.' });
-                }
-                return done(null, user);
-            });*/
         }
-    );
-    jamsStrategy.name = "jams";
+    )
+    jamsStrategy.name = "jams"
 
     const localStrategy = new LocalStrategy(
         (username, password, done) => {
-            console.log("localStrategy: " + username + " " + password);
+            console.log("localStrategy: " + username + " " + password)
 
-            const newUser = {};
-            newUser.accountId = jami.getAccountList()[0].getId();
-            console.log("Local AccountId: " + newUser.accountId);
-            connectedUsers[newUser.accountId] = newUser;
-            done(null, newUser);
+            const id = username
+            const userConfig = appConfig.users[username]
+            if (!userConfig) {
+                return done(null, false, { message: 'Incorrect username.' })
+            }
+            if (userConfig.password !== password) {
+                return done(null, false, { message: 'Incorrect password.' })
+            }
+            userConfig.type = 'local'
+
+            done(null, user(id, userConfig))
         }
-    );
+    )
 
-    passport.use(jamsStrategy);
-    passport.use(localStrategy);
+    passport.use(jamsStrategy)
+    passport.use(localStrategy)
 
     const secured = (req, res, next) => {
-        console.log(`isSecured ${req.user}`);
-        if (req.user && req.user.accountId) {
-            return next();
+        if (req.user) {
+            return next()
         }
         res.status(401).end()
-    };
+    }
     const securedRedirect = (req, res, next) => {
         if (req.user && req.user.accountId) {
-            return next();
+            return next()
         }
-        req.session.returnTo = req.originalUrl;
-        res.redirect('/login');
-    };
+        req.session.returnTo = req.originalUrl
+        res.redirect('/login')
+    }
 
-    app.post('/auth', passport.authenticate('jams'), (req, res) => {
+    app.use(express.json())
+    app.post('/setup', (req, res) => {
+        if (isSetupComplete()) {
+            return res.status(404).end()
+        }
+        if (!req.body.password) {
+            return res.status(400).end()
+        }
+        console.log(req.body)
+        appConfig.users.admin = {
+            "accounts": "*",
+            password: req.body.password
+        }
+        res.status(200).end()
+        saveConfig(configPath, appConfig)
+    })
+    app.post('/auth/jams', passport.authenticate('jams'), (req, res) => {
         res.json({ loggedin: true })
-    });
-    app.post('auth/localLogin', passport.authenticate('local'), (req, res) => {
-        res.json({ loggedin: true })
-    });
+    })
+    app.post('/auth/local', passport.authenticate('local'), (req, res) => {
+        res.json({ loggedin: true, user: req.user.id })
+    })
+    app.get('/auth', (req, res) => {
+        if (req.user) {
+            res.json({ loggedin: true, username: req.user.username, type: req.user.type })
+        } else if (isSetupComplete()) {
+            res.status(401).json({})
+        } else {
+            res.status(401).json({ setupComplete: false })
+        }
+    })
 
-    app.use('/api', secured, apiRouter);
+    app.use('/api', secured, apiRouter)
 
-    app.use('/', indexRouter);
+    app.use('/', indexRouter)
 
     /* GET React App */
 
     app.use(express.static(path.join(__dirname, 'client', 'dist')))
 
     app.use((req, res, next) => {
-        res.sendFile(path.join(__dirname, 'client', 'dist', 'index.html'));
-    });
+        res.sendFile(path.join(__dirname, 'client', 'dist', 'index.html'))
+    })
 
-    return http.Server(app);
+    return http.Server(app)
 }
 
-loadConfig()
+loadConfig(configPath)
     .then(createServer)
     .then(server => {
-        server.listen(3000);
+        server.listen(3000)
     })
 
 /*
 io.on('connection', (socket) => {
     console.log("Client just connected !")
     socket.on('SendMessage', (data) => {
-        console.log("Message " + data.text + " sent to " + data.destinationId + " by " + socket.session.user.accountId);
-        const msgMap = new jami.dring.StringMap();
-        msgMap.set('text/plain', data.text);
-        jami.dring.sendAccountTextMessage(socket.session.user.accountId, data.destinationId, msgMap);
-    });
-});
+        console.log("Message " + data.text + " sent to " + data.destinationId + " by " + socket.session.user.accountId)
+        const msgMap = new jami.dring.StringMap()
+        msgMap.set('text/plain', data.text)
+        jami.dring.sendAccountTextMessage(socket.session.user.accountId, data.destinationId, msgMap)
+    })
+})
 
 io.use((socket, next) => {
     cookieParser(socket.handshake, {}, (err) => {
         if (err) {
-            console.log("error in parsing cookie");
-            return next(err);
+            console.log("error in parsing cookie")
+            return next(err)
         }
         if (!socket.handshake.signedCookies) {
-            console.log("no secureCookies|signedCookies found");
-            return next(new Error("no secureCookies found"));
+            console.log("no secureCookies|signedCookies found")
+            return next(new Error("no secureCookies found"))
         }
         sessionStore.get(socket.handshake.signedCookies["connect.sid"], (err, session) => {
-            socket.session = session;
-            if (!err && !session) err = new Error('session not found');
+            socket.session = session
+            if (!err && !session) err = new Error('session not found')
             if (err) {
-                console.log('failed connection to socket.io:', err);
+                console.log('failed connection to socket.io:', err)
             } else {
-                console.log(session);
-                console.log('successful connection to socket.io ' + session.passport.user);
-                const userKey = session.passport.user;
+                console.log(session)
+                console.log('successful connection to socket.io ' + session.passport.user)
+                const userKey = session.passport.user
                 deserializeUser(userKey, (err, user) => {
                     console.log("deserializeUser: " + user)
                     if (err)
-                        return next(err, true);
+                        return next(err, true)
                     if (!user)
-                        return next("User not found", false);
+                        return next("User not found", false)
 
                     console.log("User associated socket id: " + socket.id)
-                    user.socketId = socket.id;
-                    socket.session.user = user;
-                    console.log("User added to session --------> " + user.accountId);
-                    //auth.success(data, accept);
-                    next(err, true);
-                });
+                    user.socketId = socket.id
+                    socket.session.user = user
+                    console.log("User added to session --------> " + user.accountId)
+                    //auth.success(data, accept)
+                    next(err, true)
+                })
             }
-        });
-    });
-});
+        })
+    })
+})
 */
diff --git a/client/package.json b/client/package.json
index 56ad635..6cd7d4d 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,11 +1,13 @@
 {
   "name": "jami-web-client",
+  "type": "module",
   "version": "0.1.0",
   "private": true,
   "dependencies": {
-    "@material-ui/core": "^4.10.2",
-    "@material-ui/icons": "^4.9.1",
-    "@material-ui/lab": "^4.0.0-alpha.56",
+    "@babel/runtime": "^7.13.10",
+    "@material-ui/core": "^4.11.3",
+    "@material-ui/icons": "^4.11.2",
+    "@material-ui/lab": "^4.0.0-alpha.57",
     "@testing-library/jest-dom": "^4.2.4",
     "@testing-library/react": "^9.5.0",
     "@testing-library/user-event": "^7.2.1",
@@ -18,12 +20,15 @@
     "socket.io-client": "^2.3.0"
   },
   "devDependencies": {
+    "@babel/plugin-transform-runtime": "^7.13.15",
     "@babel/core": "^7.13.14",
     "@babel/preset-env": "^7.13.12",
     "@babel/preset-react": "^7.13.13",
+    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-beta.3",
     "babel-loader": "^8.2.2",
     "css-loader": "^5.2.0",
     "html-webpack-plugin": "^5.3.1",
+    "react-refresh": "^0.10.0",
     "sass": "^1.32.8",
     "sass-loader": "^11.0.1",
     "style-loader": "^2.0.0",
diff --git a/client/src/App.js b/client/src/App.js
index 10db90a..cc1b5bf 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -3,52 +3,59 @@
   Author: Larbi Gharib <larbi.gharib@savoirfairelinux.com>
   License: AGPL-3
 */
-
-import React from 'react';
-import CssBaseline from '@material-ui/core/CssBaseline';
+import React, { useState, useEffect } from 'react'
+import { Route, Switch, Redirect, useHistory, useLocation } from 'react-router-dom'
+import { CircularProgress, Container, CssBaseline } from '@material-ui/core'
 import authManager from './AuthManager'
-//import logo from './logo.svg';
-import './App.scss';
-
-import { BrowserRouter as Router, Route, Switch, Link, Redirect } from 'react-router-dom';
+//import logo from './logo.svg'
+import './App.scss'
 
 import SignInPage from "./pages/loginDialog.jsx"
 import JamiMessenger from "./pages/messenger.jsx"
 import AccountSettings from "./pages/accountSettings.jsx"
 import AccountSelection from "./pages/accountSelection.jsx"
-import AddContactPage from "./pages/addContactPage.jsx"
-
+import ServerSetup from "./pages/serverSetup.jsx"
 import NotFoundPage from "./pages/404.jsx"
 
-class App extends React.Component {
-  constructor(props) {
-    super(props);
-    this.state = {
-      authenticated: authManager.isAuthenticated(),
-    };
-    authManager.setOnAuthChanged(authenticated => this.setState({authenticated}))
-  }
+const App = (props) => {
+    const history = useHistory()
+    const { location } = useLocation()
+    const [state, setState] = useState({
+      loaded: false,
+      auth: authManager.getState()
+    })
+    useEffect(() => {
+      authManager.init(auth => {
+        setState({ loaded: true, auth })
+      })
+      return () => authManager.deinit()
+    }, []);
 
-  render() {
     console.log("App render")
-    console.log(this.props)
+    console.log(state)
+    console.log(location)
 
-      return <React.Fragment>
-        <CssBaseline />
-        <Router>
-          <Switch>
-            <Route exact path="/"><Redirect to="/account" /></Route>
-            <Route path="/account/:accountId/settings" component={AccountSettings} />
-            <Route path="/account/:accountId/addContact/:contactId" component={JamiMessenger} />
-            <Route path="/account/:accountId/conversation/:conversationId" component={JamiMessenger} />
-            <Route path="/account/:accountId" component={JamiMessenger} />
-            <Route path="/account" component={AccountSelection} />
-            <Route component={NotFoundPage} />
-          </Switch>
-        </Router>
-        {!this.state.authenticated && <SignInPage open={!this.state.authenticated}/>}
-      </React.Fragment>
-  }
+    if (!state.loaded) {
+      return <Container><CircularProgress /></Container>
+    } else if (!state.auth.setupComplete) {
+      return <Switch>
+          <Route path="/setup" component={ServerSetup} />
+          <Route><Redirect to="/setup" /></Route>
+        </Switch>
+    }
+    return <React.Fragment>
+      <CssBaseline />
+        <Switch>
+          <Route exact path="/"><Redirect to="/account" /></Route>
+          <Route path="/account/:accountId/settings" component={AccountSettings} />
+          <Route path="/account/:accountId/addContact/:contactId" component={JamiMessenger} />
+          <Route path="/account/:accountId/conversation/:conversationId" component={JamiMessenger} />
+          <Route path="/account/:accountId" component={JamiMessenger} />
+          <Route path="/account" component={AccountSelection} />
+          <Route component={NotFoundPage} />
+        </Switch>
+      {!state.auth.authenticated && <SignInPage open={!state.auth.authenticated}/>}
+    </React.Fragment>
 }
 
-export default App
\ No newline at end of file
+export default App
diff --git a/client/src/AuthManager.js b/client/src/AuthManager.js
index 09aef64..4f504db 100644
--- a/client/src/AuthManager.js
+++ b/client/src/AuthManager.js
@@ -17,39 +17,114 @@
  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
-//import cookie from 'cookie';
-
 class AuthManager {
     constructor() {
         console.log("AuthManager()")
-        this.authenticated = true//'connect.sid' in cookie.parse(document.cookie)
         this.authenticating = false
+
+        this.state = {
+            initialized: false,
+            authenticated: true,
+            setupComplete: true,
+            error: false
+        }
+
         this.tasks = []
         this.onAuthChanged = undefined
     }
-    setOnAuthChanged(onAuthChanged) {
-        this.onAuthChanged = onAuthChanged
-    }
 
     isAuthenticated() {
-        return this.authenticated
+        return this.state.authenticated
     }
 
-    authenticate() {
+    getState() {
+        return this.state
+    }
+
+    init(cb) {
+        this.onAuthChanged = cb
+        if (this.state.initialized || this.authenticating)
+            return
+        console.log("Init")
+        this.authenticating = true
+        fetch('/auth')
+            .then(async (response) => {
+                this.authenticating = false
+                this.state.initialized = true
+                console.log("Init ended")
+                console.log(response)
+                if (response.status === 200) {
+                    const jsonData = await response.json()
+                    Object.assign(this.state, {
+                        authenticated: true,
+                        setupComplete: true,
+                        error: false,
+                        user: { username: jsonData.username, type: jsonData.type }
+                    })
+                } else if (response.status === 401) {
+                    const jsonData = await response.json()
+                    Object.assign(this.state, {
+                        authenticated: false,
+                        setupComplete: 'setupComplete' in jsonData ? jsonData.setupComplete : true,
+                        error: false
+                    })
+                } else {
+                    this.state.error = true
+                }
+                console.log("New auth state")
+                console.log(this.state)
+
+                if (this.onAuthChanged)
+                    this.onAuthChanged(this.state)
+            })
+    }
+
+    deinit() {
+        console.log("Deinit")
+        this.onAuthChanged = undefined
+    }
+
+    async setup(password) {
+        if (this.authenticating || this.state.setupComplete)
+            return
+        console.log("Starting setup")
+        this.authenticating = true
+        const response = await fetch(`/setup`, {
+            method: 'POST',
+            headers: {
+                'Accept': 'application/json',
+                'Content-Type': 'application/json'
+            },
+            body: JSON.stringify({ password })
+        })
+        console.log(response)
+        if (response.ok) {
+            console.log("Success, going home")
+            //history.replace('/')
+        } else {
+        }
+        this.authenticating = false
+        this.state.setupComplete = true
+        if (this.onAuthChanged)
+            this.onAuthChanged(this.state)
+        return response.ok
+    }
+
+    authenticate(username, password) {
         if (this.authenticating)
             return
         console.log("Starting authentication")
         this.authenticating = true
-        fetch('/api/localLogin?username=local&password=local', { method:"POST" })
+        fetch(`/auth/local?username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`, { method:"POST" })
             .then(response => {
                 console.log(response)
                 this.authenticating = false
-                this.authenticated = response.ok && response.status === 200
+                this.state.authenticated = response.ok && response.status === 200
                 if (this.onAuthChanged)
-                    this.onAuthChanged(this.authenticated)
+                    this.onAuthChanged(this.state)
                 while (this.tasks.length !== 0) {
                     const task = this.tasks.shift()
-                    if (this.authenticated)
+                    if (this.state.authenticated)
                         fetch(task.url, task.init).then(res => task.resolve(res))
                     else
                         task.reject(new Error("Authentication failed"))
@@ -59,14 +134,14 @@
 
     disconnect() {
         console.log("Disconnect")
-        this.authenticated = false
+        this.state.authenticated = false
         if (this.onAuthChanged)
-            this.onAuthChanged(this.authenticated)
+            this.onAuthChanged(this.state)
     }
 
     fetch(url, init) {
         console.log(`get ${url}`)
-        if (!this.authenticated) {
+        if (!this.state.authenticated) {
             return new Promise((resolve, reject) => this.tasks.push({url, init, resolve, reject}))
         }
         return fetch(url, init)
diff --git a/client/src/components/AccountList.js b/client/src/components/AccountList.js
index 6f38c3c..38f1291 100644
--- a/client/src/components/AccountList.js
+++ b/client/src/components/AccountList.js
@@ -1,29 +1,19 @@
-import React from 'react';
+import React from 'react'
+import { Avatar, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'
+import { PersonRounded } from '@material-ui/icons';
 
-import List from '@material-ui/core/List';
-import ListItem from '@material-ui/core/ListItem';
-import ListItemText from '@material-ui/core/ListItemText';
-import ListItemAvatar from '@material-ui/core/ListItemAvatar';
-import Avatar from '@material-ui/core/Avatar';
-import PersonRoundedIcon from '@material-ui/icons/PersonRounded';
-
-class AccountList extends React.Component {
-  render() {
-    return (
-        <List>
-          {
-            this.props.accounts.map(account => <ListItem button key={account.getId()} onClick={() => this.props.onClick(account)}>
-              <ListItemAvatar>
-                <Avatar>
-                  <PersonRoundedIcon />
-                </Avatar>
-              </ListItemAvatar>
-              <ListItemText primary={account.getDisplayName()} secondary={account.getDisplayUri()} />
-            </ListItem>
-            )
-          }
-        </List>)
-  }
+export default function AccountList(props) {
+  return <List>
+    {
+      props.accounts.map(account => {
+        const displayName = account.getDisplayNameNoFallback()
+        return <ListItem button key={account.getId()} onClick={() => props.onClick(account)}>
+          <ListItemAvatar>
+            <Avatar>{displayName ? displayName[0].toUpperCase() : <PersonRounded />}</Avatar>
+          </ListItemAvatar>
+          <ListItemText primary={account.getDisplayName()} secondary={account.getDisplayUri()} />
+        </ListItem>
+      })
+    }
+  </List>
 }
-
-export default AccountList;
\ No newline at end of file
diff --git a/client/src/components/ConversationListItem.js b/client/src/components/ConversationListItem.js
index 7e13974..a769168 100644
--- a/client/src/components/ConversationListItem.js
+++ b/client/src/components/ConversationListItem.js
@@ -2,7 +2,7 @@
 import React from 'react'
 import Conversation from '../../../model/Conversation'
 import { useHistory, useParams } from "react-router-dom"
-import PersonIcon from '@material-ui/icons/PersonRounded'
+import { PersonRounded } from '@material-ui/icons'
 
 export default function ConversationListItem(props) {
     const { conversationId, contactId } = useParams()
@@ -22,7 +22,7 @@
                 style={{overflow:'hidden'}}
                 onClick={() => history.push(`/account/${conversation.getAccountId()}/${uri}`)}>
                 <ListItemAvatar>
-                    <Avatar>{displayName ? displayName[0].toUpperCase() : <PersonIcon />}</Avatar>
+                    <Avatar>{displayName ? displayName[0].toUpperCase() : <PersonRounded />}</Avatar>
                 </ListItemAvatar>
                 <ListItemText
                     style={{overflow:'hidden', textOverflow:'ellipsis'}}
diff --git a/client/src/components/Header.js b/client/src/components/Header.js
index d245976..c0408a1 100644
--- a/client/src/components/Header.js
+++ b/client/src/components/Header.js
@@ -32,7 +32,7 @@
             <Menu
                 id="simple-menu"
                 anchorEl={anchorEl}
-                keepMounted
+
                 open={Boolean(anchorEl)}
                 onClose={handleClose}
             >
diff --git a/client/src/components/MessageList.js b/client/src/components/MessageList.js
index d856c17..4841ea1 100644
--- a/client/src/components/MessageList.js
+++ b/client/src/components/MessageList.js
@@ -1,7 +1,7 @@
-import { Avatar, Box, Divider, Typography } from '@material-ui/core'
-import React from 'react'
 import Message from './Message'
-import PersonIcon from '@material-ui/icons/PersonRounded'
+import React from 'react'
+import { Avatar, Box, Divider, Typography } from '@material-ui/core'
+import { PersonRounded } from '@material-ui/icons'
 
 export default function MessageList(props) {
     const displayName = props.conversation.getDisplayName()
@@ -10,7 +10,7 @@
         <div className="message-list">
             <Box>
                 <Box style={{ display: 'inline-block', margin: 16, verticalAlign: 'middle' }}>
-                    <Avatar>{displayName ? displayName[0].toUpperCase() : <PersonIcon />}</Avatar>
+                    <Avatar>{displayName ? displayName[0].toUpperCase() : <PersonRounded />}</Avatar>
                 </Box>
                 <Box style={{ display: 'inline-block', verticalAlign: 'middle' }}>
                     <Typography variant="h5">{displayName}</Typography>
diff --git a/client/src/components/NewContactForm.js b/client/src/components/NewContactForm.js
index c98c9b4..5636be2 100644
--- a/client/src/components/NewContactForm.js
+++ b/client/src/components/NewContactForm.js
@@ -1,7 +1,6 @@
 import React from 'react'
-import SearchIcon from '@material-ui/icons/Search';
-import InputBase from '@material-ui/core/InputBase';
-import InputAdornment from '@material-ui/core/InputAdornment';
+import { InputBase, InputAdornment } from '@material-ui/core';
+import { SearchRounded } from '@material-ui/icons';
 
 class NewContactForm extends React.Component {
     constructor(props) {
@@ -40,11 +39,8 @@
                     type="search"
                     placeholder="Ajouter un contact"
                     onChange={this.handleChange}
-                    startAdornment={
-                        <InputAdornment position="start">
-                            <SearchIcon />
-                        </InputAdornment>
-                        } />
+                    startAdornment={<InputAdornment position="start"><SearchRounded /></InputAdornment>}
+                />
             </form>
         )
     }
diff --git a/client/src/components/SendMessageForm.js b/client/src/components/SendMessageForm.js
index 6269e3b..48770ba 100644
--- a/client/src/components/SendMessageForm.js
+++ b/client/src/components/SendMessageForm.js
@@ -1,8 +1,7 @@
 import React from 'react'
 import { makeStyles } from '@material-ui/core/styles'
 import { IconButton, InputBase, Paper, Popper } from '@material-ui/core'
-import SendIcon from '@material-ui/icons/Send'
-import EmojiIcon from '@material-ui/icons/EmojiEmotionsRounded'
+import { Send, EmojiEmotionsRounded } from '@material-ui/icons'
 import EmojiPicker from 'emoji-picker-react'
 
 const useStyles = makeStyles((theme) => ({
@@ -58,7 +57,7 @@
         className="send-message-card"
         className={classes.root}>
         <IconButton aria-describedby={id} variant="contained" color="primary" onClick={handleOpenEmojiPicker}>
-          <EmojiIcon />
+          <EmojiEmotionsRounded />
         </IconButton>
         <Popper
           id={id}
@@ -66,7 +65,7 @@
           anchorEl={anchorEl}
           onClose={handleClose}
         >
-          <EmojiPicker
+          <EmojiPicker.default
             onEmojiClick={onEmojiClick}
             disableAutoFocus={true}
             disableSkinTonePicker={true}
@@ -82,7 +81,7 @@
           onChange={handleInputChange}
         />
         <IconButton type="submit" className={classes.iconButton} aria-label="search">
-          <SendIcon />
+          <Send />
         </IconButton>
       </Paper>
     </div>
diff --git a/client/src/index.js b/client/src/index.js
index f4c1f23..054afae 100644
--- a/client/src/index.js
+++ b/client/src/index.js
@@ -1,24 +1,30 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './index.scss';
-import App from './App';
-//import * as serviceWorker from './serviceWorker';
-const rootEl = document.getElementById('root');
+'use strict'
+import React from 'react'
+import ReactDOM from 'react-dom'
+import { BrowserRouter as Router } from 'react-router-dom'
+import App from './App.js'
+import './index.scss'
+
+//import * as serviceWorker from './serviceWorker'
+const rootEl = document.getElementById('root')
 
 const render = Component =>
 ReactDOM.render(
   <React.StrictMode>
+    <Router>
     <Component />
+    </Router>
   </React.StrictMode>,
   rootEl
-);
+)
 
 // If you want your app to work offline and load faster, you can change
 // unregister() to register() below. Note this comes with some pitfalls.
 // Learn more about service workers: https://bit.ly/CRA-PWA
-//serviceWorker.unregister();
+//serviceWorker.unregister()
 render(App)
-if (module.hot) module.hot.accept('./App', () => {
+
+if (import.meta.webpackHot) import.meta.webpackHot.accept('./App', () => {
   try {
     render(App)
   } catch (e) {
diff --git a/client/src/pages/loginDialog.jsx b/client/src/pages/loginDialog.jsx
index 93f9f59..4e15ae1 100644
--- a/client/src/pages/loginDialog.jsx
+++ b/client/src/pages/loginDialog.jsx
@@ -105,7 +105,7 @@
             submitted: true,
             loading: true
         })
-        authManager.authenticate()
+        authManager.authenticate('admin', 'admin')
         /*fetch('/api/localLogin?username=none&password=none', {
             header: { "Content-Type": "application/json" },
             method: "POST",
diff --git a/client/src/pages/serverSetup.jsx b/client/src/pages/serverSetup.jsx
new file mode 100644
index 0000000..70f9342
--- /dev/null
+++ b/client/src/pages/serverSetup.jsx
@@ -0,0 +1,85 @@
+import React, { useState } from 'react';
+import { useHistory } from "react-router-dom";
+
+import { Box, Container, Fab, Card, CardContent, Typography, Input } from '@material-ui/core';
+import GroupAddRounded from '@material-ui/icons/GroupAddRounded';
+import { makeStyles } from '@material-ui/core/styles';
+import authManager from '../AuthManager'
+
+const useStyles = makeStyles((theme) => ({
+  root: {
+    '& > *': {
+      margin: theme.spacing(1),
+    },
+  },
+  extendedIcon: {
+    marginRight: theme.spacing(1),
+  },
+  wizardCard: {
+    borderRadius: 8,
+    maxWidth: 360,
+    margin: "16px auto"
+  }, textField: {
+    margin: theme.spacing(1),
+  }
+}))
+
+export default function ServerSetup(props) {
+  const classes = useStyles()
+  const history = useHistory();
+  const [password, setPassword] = useState('');
+  const [passwordRepeat, setPasswordRepeat] = useState('');
+
+  const isValid = () => password && password === passwordRepeat
+
+  const handleSubmit = async e => {
+    e.preventDefault()
+    if (!isValid())
+      return
+    if (await authManager.setup(password)) {
+      history.replace('/')
+    }
+  }
+
+  return (
+    <Container className='message-list'>
+      <Card  className={classes.wizardCard}>
+        <CardContent component="form" onSubmit={handleSubmit}>
+          <Typography gutterBottom variant="h5" component="h2">
+          Jami Web Node setup
+          </Typography>
+          <Typography variant="body2" color="textSecondary" component="p">
+            Welcome to the Jami web node setup.<br/>
+            Let's start by creating a new administrator account to control access to the server configuration.
+          </Typography>
+
+          <Typography variant='body1'></Typography>
+          <div><Input className={classes.textField} value="admin" name="username" autoComplete="username" disabled /></div>
+          <div><Input
+            className={classes.textField}
+            value={password}
+            onChange={e => setPassword(e.target.value)}
+            name="password"
+            type='password'
+            placeholder="New password"
+            autoComplete="new-password" />
+          </div>
+          <div><Input
+            className={classes.textField}
+            value={passwordRepeat}
+            onChange={e => setPasswordRepeat(e.target.value)}
+            name="password"
+            error={!!passwordRepeat && !isValid()}
+            type='password'
+            placeholder="Repeat password"
+            autoComplete="new-password" /></div>
+          <Box style={{ textAlign: 'center', marginTop: 16 }}>
+            <Fab variant='extended' color='primary' type='submit' disabled={!isValid()}>
+              <GroupAddRounded className={classes.extendedIcon} />
+              Create admin account
+            </Fab>
+          </Box>
+        </CardContent>
+      </Card>
+    </Container>)
+}
diff --git a/client/webpack.config.js b/client/webpack.config.js
index f3b38cd..82caf43 100644
--- a/client/webpack.config.js
+++ b/client/webpack.config.js
@@ -1,27 +1,38 @@
 'use strict'
-const path = require('path')
-require('dotenv').config({ path: path.resolve(__dirname, '..', '.env') })
-const HtmlWebpackPlugin = require('html-webpack-plugin')
+
+import dotenv from 'dotenv'
+dotenv.config({ path: resolve(import.meta.url, '..', '.env') })
+
+import { resolve } from 'path'
+import HtmlWebpackPlugin from 'html-webpack-plugin'
+
+import { fileURLToPath } from 'url';
+import { dirname } from 'path';
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
 const mode = process.env.NODE_ENV || 'development'
 
-let entry = [path.resolve(__dirname, 'src', 'index.js')]
+let entry = [resolve(__dirname, 'src', 'index.js')]
 let plugins = [new HtmlWebpackPlugin({
-    template: path.resolve(__dirname, 'src', 'index.ejs')
+    template: resolve(__dirname, 'src', 'index.ejs')
 })]
 let devtool = undefined
+let babelLoaderPlugins = ["@babel/plugin-transform-runtime"]
 
 if (mode === 'development') {
-    const webpack = require('webpack')
-    entry = ['react-hot-loader/patch', 'webpack-hot-middleware/client', ...entry]
-    plugins = [new webpack.HotModuleReplacementPlugin(), ...plugins]
+    const webpack = (await import('webpack')).default
+    const ReactRefreshWebpackPlugin = (await import('@pmmmwh/react-refresh-webpack-plugin')).default
+    entry = ['webpack-hot-middleware/client', ...entry]
+    plugins = [new webpack.HotModuleReplacementPlugin(), new ReactRefreshWebpackPlugin(), ...plugins]
+    babelLoaderPlugins = [...babelLoaderPlugins, "react-refresh/babel"]
     devtool = 'inline-source-map'
 }
 console.log(`Webpack configured for ${mode}`)
 
-module.exports = {
+export default {
     entry,
     output: {
-        path: path.resolve(__dirname, 'dist'),
+        path: resolve(__dirname, 'dist'),
         filename: 'bundle.js',
         publicPath: '/'
     },
@@ -31,14 +42,20 @@
         rules: [
             {
                 test: /\.jsx?/,
+                resolve: {
+                    fullySpecified: false
+                },
                 exclude: /node_modules/,
                 use: {
                     loader: 'babel-loader',
                     options: {
-                        presets: [['@babel/preset-env', {
-                            useBuiltIns: 'entry',
-                            corejs:{ version: "3.10", proposals: true },
-                        }], '@babel/preset-react']
+                        plugins: babelLoaderPlugins,
+                        presets: [
+                            ['@babel/preset-env', {
+                                useBuiltIns: 'entry',
+                                corejs:{ version: "3.10", proposals: true },
+                            }],
+                            '@babel/preset-react']
                     }
                 }
             },
diff --git a/model/Account.js b/model/Account.js
index 1f70c33..46f96ed 100644
--- a/model/Account.js
+++ b/model/Account.js
@@ -1,4 +1,4 @@
-const Contact = require('./Contact')
+import Contact from './Contact.js'
 
 class Account {
     constructor(id, details, volatileDetails) {
@@ -52,6 +52,10 @@
         return this.getRegisteredName() || this.getUri()
     }
 
+    getDisplayNameNoFallback() {
+        return this.details["Account.displayName"] || this.getRegisteredName()
+    }
+
     getConversationIds() {
         return Object.keys(this.conversations)
     }
@@ -91,4 +95,4 @@
 Account.BOOL_TRUE = "true"
 Account.BOOL_FALSE = "false"
 
-module.exports = Account
+export default Account
diff --git a/model/Contact.js b/model/Contact.js
index 9d00098..8fa26b2 100644
--- a/model/Contact.js
+++ b/model/Contact.js
@@ -30,4 +30,4 @@
     }
 }
 
-module.exports = Contact;
+export default Contact
diff --git a/model/Conversation.js b/model/Conversation.js
index 22dc477..09fb76f 100644
--- a/model/Conversation.js
+++ b/model/Conversation.js
@@ -1,4 +1,4 @@
-const Contact = require('./Contact')
+import Contact from './Contact.js'
 
 class Conversation {
     constructor(id, accountId, members) {
@@ -62,4 +62,4 @@
     }
 }
 
-module.exports = Conversation;
+export default Conversation;
diff --git a/package-lock.json b/package-lock.json
index 4b4e8fc..2042d1f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -34,19 +34,18 @@
         "jami-web-client": "file:client",
         "llnode": "^3.2.0",
         "nodemon": "^2.0.7",
-        "react-hot-loader": "^4.13.0",
         "webpack-dev-middleware": "^4.1.0",
         "webpack-hot-middleware": "^2.25.0"
       }
     },
     "client": {
-      "name": "jami-web-client",
       "version": "0.1.0",
       "dev": true,
       "dependencies": {
-        "@material-ui/core": "^4.10.2",
-        "@material-ui/icons": "^4.9.1",
-        "@material-ui/lab": "^4.0.0-alpha.56",
+        "@babel/runtime": "^7.13.10",
+        "@material-ui/core": "^4.11.3",
+        "@material-ui/icons": "^4.11.2",
+        "@material-ui/lab": "^4.0.0-alpha.57",
         "@testing-library/jest-dom": "^4.2.4",
         "@testing-library/react": "^9.5.0",
         "@testing-library/user-event": "^7.2.1",
@@ -60,11 +59,14 @@
       },
       "devDependencies": {
         "@babel/core": "^7.13.14",
+        "@babel/plugin-transform-runtime": "^7.13.15",
         "@babel/preset-env": "^7.13.12",
         "@babel/preset-react": "^7.13.13",
+        "@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-beta.3",
         "babel-loader": "^8.2.2",
         "css-loader": "^5.2.0",
         "html-webpack-plugin": "^5.3.1",
+        "react-refresh": "^0.10.0",
         "sass": "^1.32.8",
         "sass-loader": "^11.0.1",
         "style-loader": "^2.0.0",
@@ -72,6 +74,84 @@
         "webpack-cli": "^4.6.0"
       }
     },
+    "client/node_modules/@pmmmwh/react-refresh-webpack-plugin": {
+      "version": "0.5.0-beta.3",
+      "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.0-beta.3.tgz",
+      "integrity": "sha512-21vRTjPQC/Z9S6ecmRLlo/VvnLNkWdUIU6AUaouZjYVOH/vMcjDUx6LWdCqBdibL7fRygYIaUEMevO4rjcvO5w==",
+      "dev": true,
+      "dependencies": {
+        "ansi-html": "^0.0.7",
+        "core-js-pure": "^3.8.1",
+        "error-stack-parser": "^2.0.6",
+        "html-entities": "^2.1.0",
+        "loader-utils": "^2.0.0",
+        "native-url": "^0.3.4",
+        "schema-utils": "^3.0.0",
+        "source-map": "^0.7.3"
+      },
+      "engines": {
+        "node": ">= 10.13"
+      },
+      "peerDependencies": {
+        "@types/webpack": "4.x",
+        "react-refresh": "^0.10.0",
+        "sockjs-client": "^1.4.0",
+        "type-fest": "^1.0.2",
+        "webpack": ">=4.43.0 <6.0.0",
+        "webpack-dev-server": "3.x || 4.x",
+        "webpack-hot-middleware": "2.x",
+        "webpack-plugin-serve": "0.x || 1.x"
+      },
+      "peerDependenciesMeta": {
+        "@types/webpack": {
+          "optional": true
+        },
+        "sockjs-client": {
+          "optional": true
+        },
+        "type-fest": {
+          "optional": true
+        },
+        "webpack-dev-server": {
+          "optional": true
+        },
+        "webpack-hot-middleware": {
+          "optional": true
+        },
+        "webpack-plugin-serve": {
+          "optional": true
+        }
+      }
+    },
+    "client/node_modules/html-entities": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz",
+      "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==",
+      "dev": true
+    },
+    "client/node_modules/source-map": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+      "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "client/node_modules/type-fest": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.0.2.tgz",
+      "integrity": "sha512-a720oz3Kjbp3ll0zkeN9qjRhO7I34MKMhPGQiQJAmaZQZQ1lo+NWThK322f7sXV+kTg9B1Ybt16KgBXWgteT8w==",
+      "dev": true,
+      "optional": true,
+      "peer": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/@babel/code-frame": {
       "version": "7.12.13",
       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
@@ -149,6 +229,15 @@
         "semver": "bin/semver.js"
       }
     },
+    "node_modules/@babel/core/node_modules/source-map": {
+      "version": "0.5.7",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/@babel/generator": {
       "version": "7.13.9",
       "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz",
@@ -160,6 +249,15 @@
         "source-map": "^0.5.0"
       }
     },
+    "node_modules/@babel/generator/node_modules/source-map": {
+      "version": "0.5.7",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/@babel/helper-annotate-as-pure": {
       "version": "7.12.13",
       "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz",
@@ -1242,6 +1340,32 @@
         "@babel/core": "^7.0.0-0"
       }
     },
+    "node_modules/@babel/plugin-transform-runtime": {
+      "version": "7.13.15",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.15.tgz",
+      "integrity": "sha512-d+ezl76gx6Jal08XngJUkXM4lFXK/5Ikl9Mh4HKDxSfGJXmZ9xG64XT2oivBzfxb/eQ62VfvoMkaCZUKJMVrBA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.13.12",
+        "@babel/helper-plugin-utils": "^7.13.0",
+        "babel-plugin-polyfill-corejs2": "^0.2.0",
+        "babel-plugin-polyfill-corejs3": "^0.2.0",
+        "babel-plugin-polyfill-regenerator": "^0.2.0",
+        "semver": "^6.3.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-runtime/node_modules/semver": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
     "node_modules/@babel/plugin-transform-shorthand-properties": {
       "version": "7.12.13",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz",
@@ -1663,6 +1787,7 @@
       "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.57.tgz",
       "integrity": "sha512-qo/IuIQOmEKtzmRD2E4Aa6DB4A87kmY6h0uYhjUmrrgmEAgbbw9etXpWPVXuRK6AGIQCjFzV6WO2i21m1R4FCw==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "@babel/runtime": "^7.4.4",
         "@material-ui/utils": "^4.11.2",
@@ -2829,6 +2954,50 @@
         "webpack": ">=2"
       }
     },
+    "node_modules/babel-loader/node_modules/json5": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+      "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+      "dev": true,
+      "dependencies": {
+        "minimist": "^1.2.0"
+      },
+      "bin": {
+        "json5": "lib/cli.js"
+      }
+    },
+    "node_modules/babel-loader/node_modules/loader-utils": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+      "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+      "dev": true,
+      "dependencies": {
+        "big.js": "^5.2.2",
+        "emojis-list": "^3.0.0",
+        "json5": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/babel-loader/node_modules/schema-utils": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
+      "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
+      "dev": true,
+      "dependencies": {
+        "@types/json-schema": "^7.0.5",
+        "ajv": "^6.12.4",
+        "ajv-keywords": "^3.5.2"
+      },
+      "engines": {
+        "node": ">= 8.9.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      }
+    },
     "node_modules/babel-plugin-dynamic-import-node": {
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz",
@@ -3149,9 +3318,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001208",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz",
-      "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==",
+      "version": "1.0.30001210",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001210.tgz",
+      "integrity": "sha512-avmGf0Jo00I8vB0I89J4Pba48kddasErV7slu7wrkyM5uY9gE5P+B+V3hjABv8Hp4YNG2nBqIUFUXlnqNteXEA==",
       "dev": true
     },
     "node_modules/chalk": {
@@ -3242,15 +3411,6 @@
         "node": ">= 4.0"
       }
     },
-    "node_modules/clean-css/node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/cli-boxes": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz",
@@ -3320,13 +3480,10 @@
       "dev": true
     },
     "node_modules/commander": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
-      "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
-      "dev": true,
-      "engines": {
-        "node": ">= 6"
-      }
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true
     },
     "node_modules/commondir": {
       "version": "1.0.1",
@@ -3541,23 +3698,22 @@
       }
     },
     "node_modules/css-loader": {
-      "version": "5.2.1",
-      "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.1.tgz",
-      "integrity": "sha512-YCyRzlt/jgG1xanXZDG/DHqAueOtXFHeusP9TS478oP1J++JSKOyEgGW1GHVoCj/rkS+GWOlBwqQJBr9yajQ9w==",
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.2.tgz",
+      "integrity": "sha512-IS722y7Lh2Yq+acMR74tdf3faMOLRP2RfLwS0VzSS7T98IHtacMWJLku3A0OBTFHB07zAa4nWBhA8gfxwQVWGQ==",
       "dev": true,
       "dependencies": {
         "camelcase": "^6.2.0",
-        "cssesc": "^3.0.0",
         "icss-utils": "^5.1.0",
         "loader-utils": "^2.0.0",
-        "postcss": "^8.2.8",
+        "postcss": "^8.2.10",
         "postcss-modules-extract-imports": "^3.0.0",
         "postcss-modules-local-by-default": "^4.0.0",
         "postcss-modules-scope": "^3.0.0",
         "postcss-modules-values": "^4.0.0",
         "postcss-value-parser": "^4.1.0",
         "schema-utils": "^3.0.0",
-        "semver": "^7.3.4"
+        "semver": "^7.3.5"
       },
       "engines": {
         "node": ">= 10.13.0"
@@ -3582,38 +3738,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/css-loader/node_modules/loader-utils": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
-      "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
-      "dev": true,
-      "dependencies": {
-        "big.js": "^5.2.2",
-        "emojis-list": "^3.0.0",
-        "json5": "^2.1.2"
-      },
-      "engines": {
-        "node": ">=8.9.0"
-      }
-    },
-    "node_modules/css-loader/node_modules/schema-utils": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
-      "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
-      "dev": true,
-      "dependencies": {
-        "@types/json-schema": "^7.0.6",
-        "ajv": "^6.12.5",
-        "ajv-keywords": "^3.5.2"
-      },
-      "engines": {
-        "node": ">= 10.13.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/webpack"
-      }
-    },
     "node_modules/css-loader/node_modules/semver": {
       "version": "7.3.5",
       "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
@@ -3669,15 +3793,6 @@
       "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=",
       "dev": true
     },
-    "node_modules/css/node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/cssesc": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -3835,12 +3950,6 @@
         }
       ]
     },
-    "node_modules/dom-walk": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
-      "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==",
-      "dev": true
-    },
     "node_modules/domelementtype": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
@@ -3994,9 +4103,9 @@
       }
     },
     "node_modules/engine.io-client/node_modules/ws": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
-      "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
+      "version": "7.4.5",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
+      "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
       "engines": {
         "node": ">=8.3.0"
       },
@@ -4048,9 +4157,9 @@
       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
     },
     "node_modules/engine.io/node_modules/ws": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
-      "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
+      "version": "7.4.5",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
+      "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
       "engines": {
         "node": ">=8.3.0"
       },
@@ -4121,6 +4230,15 @@
         "node": ">=4"
       }
     },
+    "node_modules/error-stack-parser": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz",
+      "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==",
+      "dev": true,
+      "dependencies": {
+        "stackframe": "^1.1.1"
+      }
+    },
     "node_modules/es-module-lexer": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz",
@@ -4360,12 +4478,6 @@
       "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
       "dev": true
     },
-    "node_modules/fast-levenshtein": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
-      "dev": true
-    },
     "node_modules/fast-xml-parser": {
       "version": "3.19.0",
       "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz",
@@ -4571,16 +4683,6 @@
       "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
       "dev": true
     },
-    "node_modules/global": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
-      "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
-      "dev": true,
-      "dependencies": {
-        "min-document": "^2.19.0",
-        "process": "^0.11.10"
-      }
-    },
     "node_modules/global-dirs": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz",
@@ -4758,6 +4860,38 @@
         "node": ">=6"
       }
     },
+    "node_modules/html-minifier-terser/node_modules/commander": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+      "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/html-minifier-terser/node_modules/terser": {
+      "version": "4.8.0",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
+      "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
+      "dev": true,
+      "dependencies": {
+        "commander": "^2.20.0",
+        "source-map": "~0.6.1",
+        "source-map-support": "~0.5.12"
+      },
+      "bin": {
+        "terser": "bin/terser"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true
+    },
     "node_modules/html-webpack-plugin": {
       "version": "5.3.1",
       "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.3.1.tgz",
@@ -5664,29 +5798,17 @@
       }
     },
     "node_modules/loader-utils": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
-      "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
+      "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
       "dev": true,
       "dependencies": {
         "big.js": "^5.2.2",
         "emojis-list": "^3.0.0",
-        "json5": "^1.0.1"
+        "json5": "^2.1.2"
       },
       "engines": {
-        "node": ">=4.0.0"
-      }
-    },
-    "node_modules/loader-utils/node_modules/json5": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
-      "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
-      "dev": true,
-      "dependencies": {
-        "minimist": "^1.2.0"
-      },
-      "bin": {
-        "json5": "lib/cli.js"
+        "node": ">=8.9.0"
       }
     },
     "node_modules/locate-path": {
@@ -5836,15 +5958,6 @@
         "url": "https://github.com/sindresorhus/mem?sponsor=1"
       }
     },
-    "node_modules/mem/node_modules/mimic-fn": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
-      "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/memfs": {
       "version": "3.2.2",
       "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.2.2.tgz",
@@ -5913,12 +6026,12 @@
       }
     },
     "node_modules/mimic-fn": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
+      "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
       "dev": true,
       "engines": {
-        "node": ">=6"
+        "node": ">=8"
       }
     },
     "node_modules/mimic-response": {
@@ -5930,15 +6043,6 @@
         "node": ">=4"
       }
     },
-    "node_modules/min-document": {
-      "version": "2.19.0",
-      "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
-      "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
-      "dev": true,
-      "dependencies": {
-        "dom-walk": "^0.1.0"
-      }
-    },
     "node_modules/min-indent": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -6124,6 +6228,15 @@
         "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
       }
     },
+    "node_modules/native-url": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/native-url/-/native-url-0.3.4.tgz",
+      "integrity": "sha512-6iM8R99ze45ivyH8vybJ7X0yekIcPf5GgLV5K0ENCbmRcaRIDoj37BC8iLEmaaBfqqb8enuZ5p0uhY+lVAbAcA==",
+      "dev": true,
+      "dependencies": {
+        "querystring": "^0.2.0"
+      }
+    },
     "node_modules/negotiator": {
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
@@ -6214,9 +6327,6 @@
       },
       "bin": {
         "nopt": "bin/nopt.js"
-      },
-      "engines": {
-        "node": "*"
       }
     },
     "node_modules/normalize-path": {
@@ -6336,6 +6446,15 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/onetime/node_modules/mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/optional-require": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz",
@@ -6363,15 +6482,15 @@
       }
     },
     "node_modules/p-limit": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
-      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
       "dev": true,
       "dependencies": {
-        "p-try": "^2.0.0"
+        "yocto-queue": "^0.1.0"
       },
       "engines": {
-        "node": ">=6"
+        "node": ">=10"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
@@ -6389,6 +6508,21 @@
         "node": ">=8"
       }
     },
+    "node_modules/p-locate/node_modules/p-limit": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "dev": true,
+      "dependencies": {
+        "p-try": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/p-try": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
@@ -6681,15 +6815,6 @@
       "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
       "dev": true
     },
-    "node_modules/postcss/node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/prepend-http": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
@@ -6930,55 +7055,20 @@
       "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==",
       "dev": true
     },
-    "node_modules/react-hot-loader": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.0.tgz",
-      "integrity": "sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA==",
-      "dev": true,
-      "dependencies": {
-        "fast-levenshtein": "^2.0.6",
-        "global": "^4.3.0",
-        "hoist-non-react-statics": "^3.3.0",
-        "loader-utils": "^1.1.0",
-        "prop-types": "^15.6.1",
-        "react-lifecycles-compat": "^3.0.4",
-        "shallowequal": "^1.1.0",
-        "source-map": "^0.7.3"
-      },
-      "engines": {
-        "node": ">= 6"
-      },
-      "peerDependencies": {
-        "@types/react": "^15.0.0 || ^16.0.0 || ^17.0.0 ",
-        "react": "^15.0.0 || ^16.0.0 || ^17.0.0 ",
-        "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 "
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/react-hot-loader/node_modules/source-map": {
-      "version": "0.7.3",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
-      "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 8"
-      }
-    },
     "node_modules/react-is": {
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
       "dev": true
     },
-    "node_modules/react-lifecycles-compat": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
-      "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
-      "dev": true
+    "node_modules/react-refresh": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
+      "integrity": "sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
     },
     "node_modules/react-router": {
       "version": "5.2.0",
@@ -7398,12 +7488,12 @@
       }
     },
     "node_modules/sass": {
-      "version": "1.32.8",
-      "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.8.tgz",
-      "integrity": "sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==",
+      "version": "1.32.10",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.10.tgz",
+      "integrity": "sha512-Nx0pcWoonAkn7CRp0aE/hket1UP97GiR1IFw3kcjV3pnenhWgZEWUf0ZcfPOV2fK52fnOcK3JdC/YYZ9E47DTQ==",
       "dev": true,
       "dependencies": {
-        "chokidar": ">=2.0.0 <4.0.0"
+        "chokidar": ">=3.0.0 <4.0.0"
       },
       "bin": {
         "sass": "sass.js"
@@ -7457,17 +7547,17 @@
       }
     },
     "node_modules/schema-utils": {
-      "version": "2.7.1",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
-      "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
+      "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
       "dev": true,
       "dependencies": {
-        "@types/json-schema": "^7.0.5",
-        "ajv": "^6.12.4",
+        "@types/json-schema": "^7.0.6",
+        "ajv": "^6.12.5",
         "ajv-keywords": "^3.5.2"
       },
       "engines": {
-        "node": ">= 8.9.0"
+        "node": ">= 10.13.0"
       },
       "funding": {
         "type": "opencollective",
@@ -7580,12 +7670,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/shallowequal": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
-      "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
-      "dev": true
-    },
     "node_modules/shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -7743,9 +7827,9 @@
       "dev": true
     },
     "node_modules/source-map": {
-      "version": "0.5.7",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
       "dev": true,
       "engines": {
         "node": ">=0.10.0"
@@ -7774,15 +7858,6 @@
         "source-map": "^0.6.0"
       }
     },
-    "node_modules/source-map-support/node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/source-map-url": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
@@ -7798,6 +7873,12 @@
         "memory-pager": "^1.0.2"
       }
     },
+    "node_modules/stackframe": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz",
+      "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==",
+      "dev": true
+    },
     "node_modules/statuses": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@@ -7901,38 +7982,6 @@
         "webpack": "^4.0.0 || ^5.0.0"
       }
     },
-    "node_modules/style-loader/node_modules/loader-utils": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
-      "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
-      "dev": true,
-      "dependencies": {
-        "big.js": "^5.2.2",
-        "emojis-list": "^3.0.0",
-        "json5": "^2.1.2"
-      },
-      "engines": {
-        "node": ">=8.9.0"
-      }
-    },
-    "node_modules/style-loader/node_modules/schema-utils": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
-      "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
-      "dev": true,
-      "dependencies": {
-        "@types/json-schema": "^7.0.6",
-        "ajv": "^6.12.5",
-        "ajv-keywords": "^3.5.2"
-      },
-      "engines": {
-        "node": ">= 10.13.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/webpack"
-      }
-    },
     "node_modules/supports-color": {
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -7976,20 +8025,20 @@
       }
     },
     "node_modules/terser": {
-      "version": "4.8.0",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
-      "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
+      "version": "5.6.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz",
+      "integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==",
       "dev": true,
       "dependencies": {
         "commander": "^2.20.0",
-        "source-map": "~0.6.1",
-        "source-map-support": "~0.5.12"
+        "source-map": "~0.7.2",
+        "source-map-support": "~0.5.19"
       },
       "bin": {
         "terser": "bin/terser"
       },
       "engines": {
-        "node": ">=6.0.0"
+        "node": ">=10"
       }
     },
     "node_modules/terser-webpack-plugin": {
@@ -8016,72 +8065,7 @@
         "webpack": "^5.1.0"
       }
     },
-    "node_modules/terser-webpack-plugin/node_modules/commander": {
-      "version": "2.20.3",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
-      "dev": true
-    },
-    "node_modules/terser-webpack-plugin/node_modules/p-limit": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
-      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
-      "dev": true,
-      "dependencies": {
-        "yocto-queue": "^0.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
-      "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
-      "dev": true,
-      "dependencies": {
-        "@types/json-schema": "^7.0.6",
-        "ajv": "^6.12.5",
-        "ajv-keywords": "^3.5.2"
-      },
-      "engines": {
-        "node": ">= 10.13.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/webpack"
-      }
-    },
-    "node_modules/terser-webpack-plugin/node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/terser-webpack-plugin/node_modules/terser": {
-      "version": "5.6.1",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz",
-      "integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==",
-      "dev": true,
-      "dependencies": {
-        "commander": "^2.20.0",
-        "source-map": "~0.7.2",
-        "source-map-support": "~0.5.19"
-      },
-      "bin": {
-        "terser": "bin/terser"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/terser-webpack-plugin/node_modules/terser/node_modules/source-map": {
+    "node_modules/terser/node_modules/source-map": {
       "version": "0.7.3",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
       "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
@@ -8090,21 +8074,6 @@
         "node": ">= 8"
       }
     },
-    "node_modules/terser/node_modules/commander": {
-      "version": "2.20.3",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
-      "dev": true
-    },
-    "node_modules/terser/node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/tiny-invariant": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
@@ -8571,24 +8540,6 @@
         "webpack": "^4.0.0 || ^5.0.0"
       }
     },
-    "node_modules/webpack-dev-middleware/node_modules/schema-utils": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
-      "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
-      "dev": true,
-      "dependencies": {
-        "@types/json-schema": "^7.0.6",
-        "ajv": "^6.12.5",
-        "ajv-keywords": "^3.5.2"
-      },
-      "engines": {
-        "node": ">= 10.13.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/webpack"
-      }
-    },
     "node_modules/webpack-hot-middleware": {
       "version": "2.25.0",
       "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.25.0.tgz",
@@ -8648,33 +8599,6 @@
         "node": ">=10.13.0"
       }
     },
-    "node_modules/webpack-sources/node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/webpack/node_modules/schema-utils": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
-      "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
-      "dev": true,
-      "dependencies": {
-        "@types/json-schema": "^7.0.6",
-        "ajv": "^6.12.5",
-        "ajv-keywords": "^3.5.2"
-      },
-      "engines": {
-        "node": ">= 10.13.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/webpack"
-      }
-    },
     "node_modules/websocket-driver": {
       "version": "0.7.4",
       "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
@@ -8863,6 +8787,12 @@
           "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
           "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
           "dev": true
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
         }
       }
     },
@@ -8875,6 +8805,14 @@
         "@babel/types": "^7.13.0",
         "jsesc": "^2.5.1",
         "source-map": "^0.5.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
       }
     },
     "@babel/helper-annotate-as-pure": {
@@ -9756,6 +9694,28 @@
         "@babel/helper-plugin-utils": "^7.12.13"
       }
     },
+    "@babel/plugin-transform-runtime": {
+      "version": "7.13.15",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.15.tgz",
+      "integrity": "sha512-d+ezl76gx6Jal08XngJUkXM4lFXK/5Ikl9Mh4HKDxSfGJXmZ9xG64XT2oivBzfxb/eQ62VfvoMkaCZUKJMVrBA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-imports": "^7.13.12",
+        "@babel/helper-plugin-utils": "^7.13.0",
+        "babel-plugin-polyfill-corejs2": "^0.2.0",
+        "babel-plugin-polyfill-corejs3": "^0.2.0",
+        "babel-plugin-polyfill-regenerator": "^0.2.0",
+        "semver": "^6.3.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
+      }
+    },
     "@babel/plugin-transform-shorthand-properties": {
       "version": "7.12.13",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz",
@@ -11055,6 +11015,39 @@
         "loader-utils": "^1.4.0",
         "make-dir": "^3.1.0",
         "schema-utils": "^2.6.5"
+      },
+      "dependencies": {
+        "json5": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "loader-utils": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+          "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^1.0.1"
+          }
+        },
+        "schema-utils": {
+          "version": "2.7.1",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
+          "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
+          "dev": true,
+          "requires": {
+            "@types/json-schema": "^7.0.5",
+            "ajv": "^6.12.4",
+            "ajv-keywords": "^3.5.2"
+          }
+        }
       }
     },
     "babel-plugin-dynamic-import-node": {
@@ -11311,9 +11304,9 @@
       "dev": true
     },
     "caniuse-lite": {
-      "version": "1.0.30001208",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz",
-      "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==",
+      "version": "1.0.30001210",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001210.tgz",
+      "integrity": "sha512-avmGf0Jo00I8vB0I89J4Pba48kddasErV7slu7wrkyM5uY9gE5P+B+V3hjABv8Hp4YNG2nBqIUFUXlnqNteXEA==",
       "dev": true
     },
     "chalk": {
@@ -11384,14 +11377,6 @@
       "dev": true,
       "requires": {
         "source-map": "~0.6.0"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        }
       }
     },
     "cli-boxes": {
@@ -11448,9 +11433,9 @@
       "dev": true
     },
     "commander": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
-      "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
       "dev": true
     },
     "commondir": {
@@ -11618,34 +11603,25 @@
         "source-map": "^0.6.1",
         "source-map-resolve": "^0.5.2",
         "urix": "^0.1.0"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        }
       }
     },
     "css-loader": {
-      "version": "5.2.1",
-      "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.1.tgz",
-      "integrity": "sha512-YCyRzlt/jgG1xanXZDG/DHqAueOtXFHeusP9TS478oP1J++JSKOyEgGW1GHVoCj/rkS+GWOlBwqQJBr9yajQ9w==",
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.2.tgz",
+      "integrity": "sha512-IS722y7Lh2Yq+acMR74tdf3faMOLRP2RfLwS0VzSS7T98IHtacMWJLku3A0OBTFHB07zAa4nWBhA8gfxwQVWGQ==",
       "dev": true,
       "requires": {
         "camelcase": "^6.2.0",
-        "cssesc": "^3.0.0",
         "icss-utils": "^5.1.0",
         "loader-utils": "^2.0.0",
-        "postcss": "^8.2.8",
+        "postcss": "^8.2.10",
         "postcss-modules-extract-imports": "^3.0.0",
         "postcss-modules-local-by-default": "^4.0.0",
         "postcss-modules-scope": "^3.0.0",
         "postcss-modules-values": "^4.0.0",
         "postcss-value-parser": "^4.1.0",
         "schema-utils": "^3.0.0",
-        "semver": "^7.3.4"
+        "semver": "^7.3.5"
       },
       "dependencies": {
         "camelcase": {
@@ -11654,28 +11630,6 @@
           "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
           "dev": true
         },
-        "loader-utils": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
-          "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
-          "dev": true,
-          "requires": {
-            "big.js": "^5.2.2",
-            "emojis-list": "^3.0.0",
-            "json5": "^2.1.2"
-          }
-        },
-        "schema-utils": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
-          "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
-          "dev": true,
-          "requires": {
-            "@types/json-schema": "^7.0.6",
-            "ajv": "^6.12.5",
-            "ajv-keywords": "^3.5.2"
-          }
-        },
         "semver": {
           "version": "7.3.5",
           "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
@@ -11849,12 +11803,6 @@
         }
       }
     },
-    "dom-walk": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
-      "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==",
-      "dev": true
-    },
     "domelementtype": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
@@ -11985,9 +11933,9 @@
           "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
         },
         "ws": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
-          "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
+          "version": "7.4.5",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
+          "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
           "requires": {}
         }
       }
@@ -12019,9 +11967,9 @@
           }
         },
         "ws": {
-          "version": "7.4.4",
-          "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
-          "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
+          "version": "7.4.5",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
+          "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
           "requires": {}
         }
       }
@@ -12074,6 +12022,15 @@
       "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==",
       "dev": true
     },
+    "error-stack-parser": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz",
+      "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==",
+      "dev": true,
+      "requires": {
+        "stackframe": "^1.1.1"
+      }
+    },
     "es-module-lexer": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz",
@@ -12262,12 +12219,6 @@
       "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
       "dev": true
     },
-    "fast-levenshtein": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
-      "dev": true
-    },
     "fast-xml-parser": {
       "version": "3.19.0",
       "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz",
@@ -12420,16 +12371,6 @@
       "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
       "dev": true
     },
-    "global": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
-      "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
-      "dev": true,
-      "requires": {
-        "min-document": "^2.19.0",
-        "process": "^0.11.10"
-      }
-    },
     "global-dirs": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz",
@@ -12573,6 +12514,33 @@
         "param-case": "^3.0.3",
         "relateurl": "^0.2.7",
         "terser": "^4.6.3"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+          "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+          "dev": true
+        },
+        "terser": {
+          "version": "4.8.0",
+          "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
+          "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
+          "dev": true,
+          "requires": {
+            "commander": "^2.20.0",
+            "source-map": "~0.6.1",
+            "source-map-support": "~0.5.12"
+          },
+          "dependencies": {
+            "commander": {
+              "version": "2.20.3",
+              "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+              "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+              "dev": true
+            }
+          }
+        }
       }
     },
     "html-webpack-plugin": {
@@ -12892,11 +12860,14 @@
       "version": "file:client",
       "requires": {
         "@babel/core": "^7.13.14",
+        "@babel/plugin-transform-runtime": "^7.13.15",
         "@babel/preset-env": "^7.13.12",
         "@babel/preset-react": "^7.13.13",
-        "@material-ui/core": "^4.10.2",
-        "@material-ui/icons": "^4.9.1",
-        "@material-ui/lab": "^4.0.0-alpha.56",
+        "@babel/runtime": "^7.13.10",
+        "@material-ui/core": "^4.11.3",
+        "@material-ui/icons": "^4.11.2",
+        "@material-ui/lab": "^4.0.0-alpha.57",
+        "@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-beta.3",
         "@testing-library/jest-dom": "^4.2.4",
         "@testing-library/react": "^9.5.0",
         "@testing-library/user-event": "^7.2.1",
@@ -12907,6 +12878,7 @@
         "react": "^16.13.1",
         "react-dom": "^16.13.1",
         "react-emoji-render": "^1.2.4",
+        "react-refresh": "^0.10.0",
         "react-router-dom": "^5.2.0",
         "react-sound": "^1.2.0",
         "sass": "^1.32.8",
@@ -12915,6 +12887,44 @@
         "style-loader": "^2.0.0",
         "webpack": "^5.31.0",
         "webpack-cli": "^4.6.0"
+      },
+      "dependencies": {
+        "@pmmmwh/react-refresh-webpack-plugin": {
+          "version": "0.5.0-beta.3",
+          "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.0-beta.3.tgz",
+          "integrity": "sha512-21vRTjPQC/Z9S6ecmRLlo/VvnLNkWdUIU6AUaouZjYVOH/vMcjDUx6LWdCqBdibL7fRygYIaUEMevO4rjcvO5w==",
+          "dev": true,
+          "requires": {
+            "ansi-html": "^0.0.7",
+            "core-js-pure": "^3.8.1",
+            "error-stack-parser": "^2.0.6",
+            "html-entities": "^2.1.0",
+            "loader-utils": "^2.0.0",
+            "native-url": "^0.3.4",
+            "schema-utils": "^3.0.0",
+            "source-map": "^0.7.3"
+          }
+        },
+        "html-entities": {
+          "version": "2.3.2",
+          "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz",
+          "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.7.3",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+          "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+          "dev": true
+        },
+        "type-fest": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.0.2.tgz",
+          "integrity": "sha512-a720oz3Kjbp3ll0zkeN9qjRhO7I34MKMhPGQiQJAmaZQZQ1lo+NWThK322f7sXV+kTg9B1Ybt16KgBXWgteT8w==",
+          "dev": true,
+          "optional": true,
+          "peer": true
+        }
       }
     },
     "jest-diff": {
@@ -13343,25 +13353,14 @@
       "dev": true
     },
     "loader-utils": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
-      "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
+      "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
       "dev": true,
       "requires": {
         "big.js": "^5.2.2",
         "emojis-list": "^3.0.0",
-        "json5": "^1.0.1"
-      },
-      "dependencies": {
-        "json5": {
-          "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
-          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
-          "dev": true,
-          "requires": {
-            "minimist": "^1.2.0"
-          }
-        }
+        "json5": "^2.1.2"
       }
     },
     "locate-path": {
@@ -13475,14 +13474,6 @@
       "requires": {
         "map-age-cleaner": "^0.1.3",
         "mimic-fn": "^3.1.0"
-      },
-      "dependencies": {
-        "mimic-fn": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
-          "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
-          "dev": true
-        }
       }
     },
     "memfs": {
@@ -13535,9 +13526,9 @@
       }
     },
     "mimic-fn": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
+      "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
       "dev": true
     },
     "mimic-response": {
@@ -13546,15 +13537,6 @@
       "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
       "dev": true
     },
-    "min-document": {
-      "version": "2.19.0",
-      "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
-      "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
-      "dev": true,
-      "requires": {
-        "dom-walk": "^0.1.0"
-      }
-    },
     "min-indent": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -13674,6 +13656,15 @@
       "integrity": "sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==",
       "dev": true
     },
+    "native-url": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/native-url/-/native-url-0.3.4.tgz",
+      "integrity": "sha512-6iM8R99ze45ivyH8vybJ7X0yekIcPf5GgLV5K0ENCbmRcaRIDoj37BC8iLEmaaBfqqb8enuZ5p0uhY+lVAbAcA==",
+      "dev": true,
+      "requires": {
+        "querystring": "^0.2.0"
+      }
+    },
     "negotiator": {
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
@@ -13833,6 +13824,14 @@
       "dev": true,
       "requires": {
         "mimic-fn": "^2.1.0"
+      },
+      "dependencies": {
+        "mimic-fn": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+          "dev": true
+        }
       }
     },
     "optional-require": {
@@ -13853,12 +13852,12 @@
       "dev": true
     },
     "p-limit": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
-      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
       "dev": true,
       "requires": {
-        "p-try": "^2.0.0"
+        "yocto-queue": "^0.1.0"
       }
     },
     "p-locate": {
@@ -13868,6 +13867,17 @@
       "dev": true,
       "requires": {
         "p-limit": "^2.2.0"
+      },
+      "dependencies": {
+        "p-limit": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+          "dev": true,
+          "requires": {
+            "p-try": "^2.0.0"
+          }
+        }
       }
     },
     "p-try": {
@@ -14036,14 +14046,6 @@
         "colorette": "^1.2.2",
         "nanoid": "^3.1.22",
         "source-map": "^0.6.1"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        }
       }
     },
     "postcss-modules-extract-imports": {
@@ -14298,40 +14300,16 @@
         }
       }
     },
-    "react-hot-loader": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.0.tgz",
-      "integrity": "sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA==",
-      "dev": true,
-      "requires": {
-        "fast-levenshtein": "^2.0.6",
-        "global": "^4.3.0",
-        "hoist-non-react-statics": "^3.3.0",
-        "loader-utils": "^1.1.0",
-        "prop-types": "^15.6.1",
-        "react-lifecycles-compat": "^3.0.4",
-        "shallowequal": "^1.1.0",
-        "source-map": "^0.7.3"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.7.3",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
-          "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
-          "dev": true
-        }
-      }
-    },
     "react-is": {
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
       "dev": true
     },
-    "react-lifecycles-compat": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
-      "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
+    "react-refresh": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
+      "integrity": "sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==",
       "dev": true
     },
     "react-router": {
@@ -14683,12 +14661,12 @@
       }
     },
     "sass": {
-      "version": "1.32.8",
-      "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.8.tgz",
-      "integrity": "sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==",
+      "version": "1.32.10",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.10.tgz",
+      "integrity": "sha512-Nx0pcWoonAkn7CRp0aE/hket1UP97GiR1IFw3kcjV3pnenhWgZEWUf0ZcfPOV2fK52fnOcK3JdC/YYZ9E47DTQ==",
       "dev": true,
       "requires": {
-        "chokidar": ">=2.0.0 <4.0.0"
+        "chokidar": ">=3.0.0 <4.0.0"
       }
     },
     "sass-loader": {
@@ -14712,13 +14690,13 @@
       }
     },
     "schema-utils": {
-      "version": "2.7.1",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
-      "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
+      "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
       "dev": true,
       "requires": {
-        "@types/json-schema": "^7.0.5",
-        "ajv": "^6.12.4",
+        "@types/json-schema": "^7.0.6",
+        "ajv": "^6.12.5",
         "ajv-keywords": "^3.5.2"
       }
     },
@@ -14811,12 +14789,6 @@
         "kind-of": "^6.0.2"
       }
     },
-    "shallowequal": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
-      "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
-      "dev": true
-    },
     "shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -14972,9 +14944,9 @@
       "dev": true
     },
     "source-map": {
-      "version": "0.5.7",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
       "dev": true
     },
     "source-map-resolve": {
@@ -14998,14 +14970,6 @@
       "requires": {
         "buffer-from": "^1.0.0",
         "source-map": "^0.6.0"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        }
       }
     },
     "source-map-url": {
@@ -15023,6 +14987,12 @@
         "memory-pager": "^1.0.2"
       }
     },
+    "stackframe": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz",
+      "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==",
+      "dev": true
+    },
     "statuses": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@@ -15096,30 +15066,6 @@
       "requires": {
         "loader-utils": "^2.0.0",
         "schema-utils": "^3.0.0"
-      },
-      "dependencies": {
-        "loader-utils": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
-          "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
-          "dev": true,
-          "requires": {
-            "big.js": "^5.2.2",
-            "emojis-list": "^3.0.0",
-            "json5": "^2.1.2"
-          }
-        },
-        "schema-utils": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
-          "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
-          "dev": true,
-          "requires": {
-            "@types/json-schema": "^7.0.6",
-            "ajv": "^6.12.5",
-            "ajv-keywords": "^3.5.2"
-          }
-        }
       }
     },
     "supports-color": {
@@ -15150,26 +15096,20 @@
       "dev": true
     },
     "terser": {
-      "version": "4.8.0",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
-      "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
+      "version": "5.6.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz",
+      "integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==",
       "dev": true,
       "requires": {
         "commander": "^2.20.0",
-        "source-map": "~0.6.1",
-        "source-map-support": "~0.5.12"
+        "source-map": "~0.7.2",
+        "source-map-support": "~0.5.19"
       },
       "dependencies": {
-        "commander": {
-          "version": "2.20.3",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
-          "dev": true
-        },
         "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "version": "0.7.3",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+          "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
           "dev": true
         }
       }
@@ -15186,59 +15126,6 @@
         "serialize-javascript": "^5.0.1",
         "source-map": "^0.6.1",
         "terser": "^5.5.1"
-      },
-      "dependencies": {
-        "commander": {
-          "version": "2.20.3",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
-          "dev": true
-        },
-        "p-limit": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
-          "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
-          "dev": true,
-          "requires": {
-            "yocto-queue": "^0.1.0"
-          }
-        },
-        "schema-utils": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
-          "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
-          "dev": true,
-          "requires": {
-            "@types/json-schema": "^7.0.6",
-            "ajv": "^6.12.5",
-            "ajv-keywords": "^3.5.2"
-          }
-        },
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        },
-        "terser": {
-          "version": "5.6.1",
-          "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz",
-          "integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==",
-          "dev": true,
-          "requires": {
-            "commander": "^2.20.0",
-            "source-map": "~0.7.2",
-            "source-map-support": "~0.5.19"
-          },
-          "dependencies": {
-            "source-map": {
-              "version": "0.7.3",
-              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
-              "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
-              "dev": true
-            }
-          }
-        }
       }
     },
     "tiny-invariant": {
@@ -15542,19 +15429,6 @@
         "terser-webpack-plugin": "^5.1.1",
         "watchpack": "^2.0.0",
         "webpack-sources": "^2.1.1"
-      },
-      "dependencies": {
-        "schema-utils": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
-          "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
-          "dev": true,
-          "requires": {
-            "@types/json-schema": "^7.0.6",
-            "ajv": "^6.12.5",
-            "ajv-keywords": "^3.5.2"
-          }
-        }
       }
     },
     "webpack-cli": {
@@ -15599,19 +15473,6 @@
         "mime-types": "^2.1.28",
         "range-parser": "^1.2.1",
         "schema-utils": "^3.0.0"
-      },
-      "dependencies": {
-        "schema-utils": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
-          "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
-          "dev": true,
-          "requires": {
-            "@types/json-schema": "^7.0.6",
-            "ajv": "^6.12.5",
-            "ajv-keywords": "^3.5.2"
-          }
-        }
       }
     },
     "webpack-hot-middleware": {
@@ -15661,14 +15522,6 @@
       "requires": {
         "source-list-map": "^2.0.1",
         "source-map": "^0.6.1"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        }
       }
     },
     "websocket-driver": {
diff --git a/package.json b/package.json
index 16825dd..75c81f2 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,6 @@
 {
   "name": "jami-web-server",
+  "type": "module",
   "version": "1.0.0",
   "description": "Jaas Web API that handles client requests to Jami daemon",
   "main": "index.js",
@@ -29,7 +30,6 @@
     "jami-web-client": "file:client",
     "llnode": "^3.2.0",
     "nodemon": "^2.0.7",
-    "react-hot-loader": "^4.13.0",
     "webpack-dev-middleware": "^4.1.0",
     "webpack-hot-middleware": "^2.25.0"
   },
diff --git a/routes/index.js b/routes/index.js
index 01e6c07..cd9bc45 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -1,10 +1,14 @@
-const express = require('express');
-const router = express.Router();
-const path = require('path');
+import { Router } from 'express'
+const router = Router()
+import { join } from 'path'
+import { fileURLToPath } from 'url';
+import { dirname } from 'path';
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
 
 /* GET React App */
 router.get(['/app', '/app/*'], (req, res, next) => {
-    res.sendFile(path.join(__dirname, '../public', 'index.html'));
-});
+    res.sendFile(join(__dirname, '../public', 'index.html'))
+})
 
-module.exports = router;
+export default router
diff --git a/routes/jami.js b/routes/jami.js
index 5d50002..674a688 100644
--- a/routes/jami.js
+++ b/routes/jami.js
@@ -1,4 +1,4 @@
-const express = require('express')
+import { Router } from 'express'
 
 class JamiRestApi {
     constructor(jami) {
@@ -6,17 +6,28 @@
     }
 
     getRouter() {
-        const router = express.Router({mergeParams: true})
-        router.use(express.json());
+        const router = Router({mergeParams: true})
+        //router.use(express.json());
 
         // Accounts
         router.get(['/accounts'], (req, res, next) => {
             console.log("Get account list")
-            res.json(this.jami.getAccountList().map(account => account.getSummary()))
+            let accounts = this.jami.getAccountList()
+            if (req.user.accountFilter)
+                accounts = accounts.filter(account => req.user.accountFilter(account.getId()))
+            res.json(accounts.map(account => account.getSummary()))
         })
 
-        const accountRouter = express.Router({mergeParams: true})
-        router.use('/accounts/:accountId', accountRouter)
+        const checkAccount = (req, res, next) => {
+            console.log(`checkAccount ${req.params.accountId} for ${req.user.id}`)
+            if (req.user && (!req.user.accountFilter || req.user.accountFilter(req.params.accountId))) {
+                return next();
+            }
+            res.status(403).end()
+        }
+
+        const accountRouter = Router({mergeParams: true})
+        router.use('/accounts/:accountId', checkAccount, accountRouter)
 
         accountRouter.get(['/'], (req, res, next) => {
             console.log(`Get account ${req.params.accountId}`)
@@ -24,7 +35,7 @@
             if (account)
                 res.json(account.getObject())
             else
-                res.sendStatus(404)
+                res.status(404).end()
         })
 
         // Contacts
@@ -34,7 +45,7 @@
             if (account)
                 res.json(account.getContacts())
             else
-                res.sendStatus(404)
+                res.status(404).end()
         })
 
         // Conversations
@@ -42,7 +53,7 @@
             console.log(`Get conversations for account ${req.params.accountId}`)
             const account = this.jami.getAccount(req.params.accountId)
             if (!account)
-                res.sendStatus(404)
+                return res.sendStatus(404)
             const conversations = account.getConversations()
             res.json(Object.keys(conversations).map(conversationId => conversations[conversationId].getObject({
                 memberFilter: member => member.contact.getUri() !== account.getUri()
@@ -55,22 +66,22 @@
             console.log(req.body)
             const account = this.jami.getAccount(req.params.accountId)
             if (!account)
-                res.sendStatus(404)
+                return res.sendStatus(404)
             if (req.body.members.length === 1) {
                 const details = this.jami.addContact(req.params.accountId, req.body.members[0])
                 res.json(details)
             } else
-                res.sendStatus(400)
+                res.status(400).end()
         })
 
         accountRouter.get('/conversations/:conversationId', (req, res, next) => {
             console.log(`Get conversation ${req.params.conversationId} for account ${req.params.accountId}`)
             const account = this.jami.getAccount(req.params.accountId)
             if (!account)
-                res.sendStatus(404)
+                return res.sendStatus(404)
             const conversation = account.getConversation(req.params.conversationId)
             if (!conversation)
-                res.sendStatus(404)
+                res.status(404).end()
             else {
                 res.json(conversation.getObject({
                     memberFilter: member => member.contact.getUri() !== account.getUri()
@@ -79,7 +90,7 @@
         })
 
         // Nameserver
-        const nsRouter = express.Router({mergeParams: true})
+        const nsRouter = Router({mergeParams: true})
         accountRouter.use('/ns', nsRouter)
 
         nsRouter.get(['/name/:nameQuery'], (req, res, next) => {
@@ -115,4 +126,4 @@
     }
 }
 
-module.exports = JamiRestApi
+export default JamiRestApi