Browse Source

upgrade to react v18 (#119)

* temp

* uprade to react 18 and react-router-dom v6, discard plugins

* fix broken filter on collections and partitions

* remove console
ryjiang 2 years ago
parent
commit
937d997f86
35 changed files with 375 additions and 588 deletions
  1. 6 6
      client/package.json
  2. 11 53
      client/src/components/customInput/SearchInput.tsx
  3. 3 3
      client/src/components/layout/Header.tsx
  4. 0 152
      client/src/components/layout/Layout.tsx
  5. 1 2
      client/src/components/menu/Types.ts
  6. 4 8
      client/src/index.tsx
  7. 3 3
      client/src/pages/collections/Collection.tsx
  8. 37 44
      client/src/pages/collections/Collections.tsx
  9. 4 4
      client/src/pages/connect/AuthForm.tsx
  10. 11 3
      client/src/pages/connect/Connect.tsx
  11. 1 1
      client/src/pages/connect/ConnectContainer.tsx
  12. 135 0
      client/src/pages/index.tsx
  13. 6 5
      client/src/pages/overview/collectionCard/CollectionCard.tsx
  14. 41 61
      client/src/pages/partitions/Partitions.tsx
  15. 0 0
      client/src/pages/search/Constants.ts
  16. 0 0
      client/src/pages/search/SearchParams.tsx
  17. 0 0
      client/src/pages/search/Styles.ts
  18. 2 2
      client/src/pages/search/Types.ts
  19. 1 1
      client/src/pages/search/VectorSearch.tsx
  20. 0 0
      client/src/pages/system/BaseCard.tsx
  21. 0 0
      client/src/pages/system/DataCard.tsx
  22. 0 0
      client/src/pages/system/LineChartCard.tsx
  23. 0 0
      client/src/pages/system/MiniTopology.tsx
  24. 0 0
      client/src/pages/system/NodeListView.tsx
  25. 0 0
      client/src/pages/system/Progress.tsx
  26. 0 0
      client/src/pages/system/ProgressCard.tsx
  27. 0 0
      client/src/pages/system/SystemView.tsx
  28. 0 0
      client/src/pages/system/Topology.tsx
  29. 0 0
      client/src/pages/system/Types.ts
  30. 0 13
      client/src/plugins/search/config.json
  31. 0 11
      client/src/plugins/system/config.json
  32. 0 58
      client/src/router/Config.ts
  33. 46 51
      client/src/router/Router.tsx
  34. 0 6
      client/src/router/Types.ts
  35. 63 101
      client/yarn.lock

+ 6 - 6
client/package.json

@@ -18,11 +18,11 @@
     "file-saver": "^2.0.5",
     "file-saver": "^2.0.5",
     "i18next": "^20.3.1",
     "i18next": "^20.3.1",
     "papaparse": "^5.3.1",
     "papaparse": "^5.3.1",
-    "react": "^17.0.2",
-    "react-dom": "^17.0.2",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
     "react-highlight-words": "^0.17.0",
     "react-highlight-words": "^0.17.0",
     "react-i18next": "^12.0.0",
     "react-i18next": "^12.0.0",
-    "react-router-dom": "^5.2.0",
+    "react-router-dom": "^6.4.3",
     "react-syntax-highlighter": "^15.4.4",
     "react-syntax-highlighter": "^15.4.4",
     "socket.io-client": "^4.1.3",
     "socket.io-client": "^4.1.3",
     "typescript": "^4.1.2",
     "typescript": "^4.1.2",
@@ -41,10 +41,10 @@
     "@types/loadable__component": "^5.13.4",
     "@types/loadable__component": "^5.13.4",
     "@types/node": "^12.0.0",
     "@types/node": "^12.0.0",
     "@types/papaparse": "^5.2.6",
     "@types/papaparse": "^5.2.6",
-    "@types/react": "^17.0.0",
-    "@types/react-dom": "^17.0.0",
+    "@types/react": "^18.0.25",
+    "@types/react-dom": "^18.0.8",
     "@types/react-highlight-words": "^0.16.2",
     "@types/react-highlight-words": "^0.16.2",
-    "@types/react-router-dom": "^5.1.7",
+    "@types/react-router-dom": "^5.3.3",
     "@types/react-syntax-highlighter": "^13.5.2",
     "@types/react-syntax-highlighter": "^13.5.2",
     "@types/webpack-env": "^1.16.3",
     "@types/webpack-env": "^1.16.3",
     "@vitest/coverage-c8": "^0.25.0",
     "@vitest/coverage-c8": "^0.25.0",

+ 11 - 53
client/src/components/customInput/SearchInput.tsx

@@ -1,7 +1,7 @@
 import { InputAdornment, makeStyles, TextField } from '@material-ui/core';
 import { InputAdornment, makeStyles, TextField } from '@material-ui/core';
 import { useRef, FC, useState, useEffect, useMemo } from 'react';
 import { useRef, FC, useState, useEffect, useMemo } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
-import { useHistory } from 'react-router-dom';
+import { useSearchParams } from 'react-router-dom';
 import Icons from '../icons/Icons';
 import Icons from '../icons/Icons';
 import { SearchType } from './Types';
 import { SearchType } from './Types';
 
 
@@ -75,64 +75,24 @@ const useSearchStyles = makeStyles(theme => ({
   },
   },
 }));
 }));
 
 
-let timer: NodeJS.Timeout | null = null;
-
 const SearchInput: FC<SearchType> = props => {
 const SearchInput: FC<SearchType> = props => {
   const { searchText = '', onClear = () => {}, onSearch = () => {} } = props;
   const { searchText = '', onClear = () => {}, onSearch = () => {} } = props;
-  const [searchValue, setSearchValue] = useState<string | null>(
-    searchText || null
-  );
-
-  const [isInit, setIsInit] = useState<boolean>(true);
-
-  const searched = useMemo(
-    () => searchValue !== '' && searchValue !== null,
-    [searchValue]
-  );
-
+  const [searchParams, setSearchParams] = useSearchParams();
+  const [searchValue, setSearchValue] = useState<string>(searchText || '');
+  const searched = useMemo(() => searchValue !== '', [searchValue]);
   const classes = useSearchStyles({ searched });
   const classes = useSearchStyles({ searched });
   const { t: commonTrans } = useTranslation();
   const { t: commonTrans } = useTranslation();
-
-  const history = useHistory();
-
   const inputRef = useRef<any>(null);
   const inputRef = useRef<any>(null);
 
 
-  const savedSearchFn = useRef<(value: string) => void>(() => {});
-  useEffect(() => {
-    savedSearchFn.current = onSearch;
-  }, [onSearch]);
+  const handleSearch = (value: string) => {
+    onSearch(value);
+  };
 
 
   useEffect(() => {
   useEffect(() => {
-    if (timer) {
-      clearTimeout(timer);
-    }
-    if (searchValue !== null && !isInit) {
-      timer = setTimeout(() => {
-        // save other params data and remove last time search info
-        const location = history.location;
-        const params = new URLSearchParams(location.search);
-        params.delete('search');
-
-        if (searchValue) {
-          params.append('search', searchValue);
-        }
-        // add search value in url
-        history.push({ search: params.toString() });
-
-        savedSearchFn.current(searchValue);
-      }, 300);
-    }
-
-    return () => {
-      timer && clearTimeout(timer);
-    };
-  }, [searchValue, history, isInit]);
-
-  const handleSearch = (value: string | null) => {
-    if (value !== null) {
-      onSearch(value);
-    }
-  };
+    searchParams[searchValue ? 'set' : 'delete']('search', searchValue);
+    setSearchParams(searchParams);
+    handleSearch(searchValue);
+  }, [searchValue]);
 
 
   return (
   return (
     <div className={classes.wrapper}>
     <div className={classes.wrapper}>
@@ -150,7 +110,6 @@ const SearchInput: FC<SearchType> = props => {
                 className={`flex-center ${classes.iconWrapper}`}
                 className={`flex-center ${classes.iconWrapper}`}
                 onClick={e => {
                 onClick={e => {
                   setSearchValue('');
                   setSearchValue('');
-                  setIsInit(false);
                   inputRef.current.focus();
                   inputRef.current.focus();
                   onClear();
                   onClear();
                 }}
                 }}
@@ -173,7 +132,6 @@ const SearchInput: FC<SearchType> = props => {
         onChange={e => {
         onChange={e => {
           const value = e.target.value.trim();
           const value = e.target.value.trim();
           setSearchValue(value);
           setSearchValue(value);
-          setIsInit(false);
           if (value === '') {
           if (value === '') {
             onClear();
             onClear();
           }
           }

+ 3 - 3
client/src/components/layout/Header.tsx

@@ -3,7 +3,7 @@ import { makeStyles, Theme, createStyles, Typography } from '@material-ui/core';
 import { HeaderType } from './Types';
 import { HeaderType } from './Types';
 import { navContext } from '../../context/Navigation';
 import { navContext } from '../../context/Navigation';
 import icons from '../icons/Icons';
 import icons from '../icons/Icons';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
 import { authContext } from '../../context/Auth';
 import { authContext } from '../../context/Auth';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import { MILVUS_ADDRESS } from '../../consts/Localstorage';
 import { MILVUS_ADDRESS } from '../../consts/Localstorage';
@@ -60,14 +60,14 @@ const Header: FC<HeaderType> = props => {
   const classes = useStyles();
   const classes = useStyles();
   const { navInfo } = useContext(navContext);
   const { navInfo } = useContext(navContext);
   const { address, setAddress, setIsAuth } = useContext(authContext);
   const { address, setAddress, setIsAuth } = useContext(authContext);
-  const history = useHistory();
+  const navigate = useNavigate();
   const { t: commonTrans } = useTranslation();
   const { t: commonTrans } = useTranslation();
   const statusTrans = commonTrans('status');
   const statusTrans = commonTrans('status');
   const BackIcon = icons.back;
   const BackIcon = icons.back;
   const LogoutIcon = icons.logout;
   const LogoutIcon = icons.logout;
 
 
   const handleBack = (path: string) => {
   const handleBack = (path: string) => {
-    history.push(path);
+    navigate(-1);
   };
   };
 
 
   const handleLogout = () => {
   const handleLogout = () => {

+ 0 - 152
client/src/components/layout/Layout.tsx

@@ -1,152 +0,0 @@
-import GlobalEffect from './GlobalEffect';
-import Header from './Header';
-import { makeStyles, Theme, createStyles } from '@material-ui/core';
-import NavMenu from '../menu/NavMenu';
-import { NavMenuItem } from '../menu/Types';
-import { useContext, useMemo } from 'react';
-import icons from '../icons/Icons';
-import { useTranslation } from 'react-i18next';
-import { useHistory, useLocation } from 'react-router-dom';
-import { authContext } from '../../context/Auth';
-import { rootContext } from '../../context/Root';
-import { IconsType } from '../icons/Types';
-
-const useStyles = makeStyles((theme: Theme) =>
-  createStyles({
-    root: {
-      minHeight: '100vh',
-      backgroundColor: '#f5f5f5',
-    },
-    content: {
-      display: 'flex',
-
-      '& .normalSearchIcon': {
-        '& path': {
-          fill: theme.palette.attuGrey.dark,
-        },
-      },
-
-      '& .activeSearchIcon': {
-        '& path': {
-          fill: theme.palette.primary.main,
-        },
-      },
-    },
-    body: {
-      flex: 1,
-      display: 'flex',
-      flexDirection: 'column',
-      height: '100vh',
-      overflowY: 'scroll',
-    },
-  })
-);
-
-const Layout = (props: any) => {
-  const history = useHistory();
-  const { isAuth } = useContext(authContext);
-  const { versionInfo } = useContext(rootContext);
-
-  const { t: navTrans } = useTranslation('nav');
-  const classes = useStyles();
-  const location = useLocation();
-  const defaultActive = useMemo(() => {
-    if (location.pathname.includes('collection')) {
-      return navTrans('collection');
-    }
-
-    if (location.pathname.includes('search')) {
-      return navTrans('search');
-    }
-
-    if (location.pathname.includes('system')) {
-      return navTrans('system');
-    }
-
-    if (location.pathname.includes('users')) {
-      return navTrans('user');
-    }
-
-    return navTrans('overview');
-  }, [location, navTrans]);
-
-  const menuItems: NavMenuItem[] = [
-    {
-      icon: icons.navOverview,
-      label: navTrans('overview'),
-      onClick: () => history.push('/'),
-    },
-    {
-      icon: icons.navCollection,
-      label: navTrans('collection'),
-      onClick: () => history.push('/collections'),
-    },
-    {
-      icon: icons.navPerson,
-      label: navTrans('user'),
-      onClick: () => history.push('/users'),
-    },
-    // {
-    //   icon: icons.navSearch,
-    //   label: navTrans('search'),
-    //   onClick: () => history.push('/search'),
-    //   iconActiveClass: 'activeSearchIcon',
-    //   iconNormalClass: 'normalSearchIcon',
-    // },
-  ];
-
-  function importAll(r: any) {
-    Object.keys(r).forEach((key: any) => {
-      const content = r[key];
-      const pathName = content.client?.path;
-
-      if (!pathName) return;
-      const result: NavMenuItem = {
-        icon: icons.navOverview,
-        label: content.client?.label || 'PLGUIN',
-      };
-      result.onClick = () => history.push(`/${pathName}`);
-      const iconName: IconsType = content.client?.iconName;
-      if (iconName) {
-        result.icon = icons[iconName];
-      }
-      content.client?.iconActiveClass &&
-        (result.iconActiveClass = content.client?.iconActiveClass);
-      content.client?.iconNormalClass &&
-        (result.iconNormalClass = content.client?.iconNormalClass);
-
-      menuItems.push(result);
-    });
-  }
-  const pluginConfigs = import.meta.glob(`../../plugins/**/config.json`, {
-    eager: true,
-  });
-
-  importAll(pluginConfigs);
-
-  return (
-    <div className={classes.root}>
-      <GlobalEffect>
-        <div className={classes.content}>
-          {isAuth && (
-            <NavMenu
-              width="200px"
-              data={menuItems}
-              defaultActive={defaultActive}
-              // used for nested child menu
-              defaultOpen={{ [navTrans('overview')]: true }}
-              versionInfo={versionInfo}
-            />
-          )}
-
-          <div className={classes.body}>
-            {isAuth && <Header />}
-            {props.children}
-          </div>
-        </div>
-      </GlobalEffect>
-    </div>
-  );
-};
-
-export default Layout;

+ 1 - 2
client/src/components/menu/Types.ts

@@ -1,6 +1,5 @@
 import { ButtonProps } from '@material-ui/core/Button';
 import { ButtonProps } from '@material-ui/core/Button';
 import { ReactElement } from 'react';
 import { ReactElement } from 'react';
-import { LoadableClassComponent } from '@loadable/component';
 
 
 export type SimpleMenuType = {
 export type SimpleMenuType = {
   label: string;
   label: string;
@@ -20,7 +19,7 @@ type CustomIcon = (
 ) => React.ReactElement<any, string | React.JSXElementConstructor<any>>;
 ) => React.ReactElement<any, string | React.JSXElementConstructor<any>>;
 
 
 export type NavMenuItem = {
 export type NavMenuItem = {
-  icon: CustomIcon | LoadableClassComponent<any>;
+  icon: CustomIcon;
   iconActiveClass?: string;
   iconActiveClass?: string;
   iconNormalClass?: string;
   iconNormalClass?: string;
   label: string;
   label: string;

+ 4 - 8
client/src/index.tsx

@@ -1,16 +1,12 @@
-import { StrictMode } from 'react';
-import ReactDOM from 'react-dom';
+import { createRoot } from 'react-dom/client';
 import './index.css';
 import './index.css';
 import './i18n';
 import './i18n';
 import App from './App';
 import App from './App';
 import reportWebVitals from './reportWebVitals';
 import reportWebVitals from './reportWebVitals';
 
 
-ReactDOM.render(
-  <StrictMode>
-    <App />
-  </StrictMode>,
-  document.getElementById('root')
-);
+const container = document.getElementById('root');
+const root = createRoot(container!); // createRoot(container!) if you use TypeScript
+root.render(<App />);
 
 
 // If you want to start measuring performance in your app, pass a function
 // If you want to start measuring performance in your app, pass a function
 // to log results (for example: reportWebVitals(console.log))
 // to log results (for example: reportWebVitals(console.log))

+ 3 - 3
client/src/pages/collections/Collection.tsx

@@ -4,7 +4,7 @@ import { ALL_ROUTER_TYPES } from '../../router/Types';
 import CustomTabList from '../../components/customTabList/CustomTabList';
 import CustomTabList from '../../components/customTabList/CustomTabList';
 import { ITab } from '../../components/customTabList/Types';
 import { ITab } from '../../components/customTabList/Types';
 import Partitions from '../partitions/Partitions';
 import Partitions from '../partitions/Partitions';
-import { useHistory, useLocation, useParams } from 'react-router-dom';
+import { useNavigate, useLocation, useParams } from 'react-router-dom';
 import { useMemo } from 'react';
 import { useMemo } from 'react';
 import { parseLocationSearch } from '../../utils/Format';
 import { parseLocationSearch } from '../../utils/Format';
 import Schema from '../schema/Schema';
 import Schema from '../schema/Schema';
@@ -23,7 +23,7 @@ const Collection = () => {
 
 
   useNavigationHook(ALL_ROUTER_TYPES.COLLECTION_DETAIL, { collectionName });
   useNavigationHook(ALL_ROUTER_TYPES.COLLECTION_DETAIL, { collectionName });
 
 
-  const history = useHistory();
+  const navigate = useNavigate();
   const location = useLocation();
   const location = useLocation();
 
 
   const { t: collectionTrans } = useTranslation('collection');
   const { t: collectionTrans } = useTranslation('collection');
@@ -37,7 +37,7 @@ const Collection = () => {
 
 
   const handleTabChange = (activeIndex: number) => {
   const handleTabChange = (activeIndex: number) => {
     const path = location.pathname;
     const path = location.pathname;
-    history.push(`${path}?activeIndex=${activeIndex}`);
+    navigate(`${path}?activeIndex=${activeIndex}`);
   };
   };
 
 
   const tabs: ITab[] = [
   const tabs: ITab[] = [

+ 37 - 44
client/src/pages/collections/Collections.tsx

@@ -1,5 +1,5 @@
 import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
 import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
-import { Link } from 'react-router-dom';
+import { Link, useSearchParams } from 'react-router-dom';
 import { useNavigationHook } from '../../hooks/Navigation';
 import { useNavigationHook } from '../../hooks/Navigation';
 import { ALL_ROUTER_TYPES } from '../../router/Types';
 import { ALL_ROUTER_TYPES } from '../../router/Types';
 import AttuGrid from '../../components/grid/Grid';
 import AttuGrid from '../../components/grid/Grid';
@@ -30,7 +30,6 @@ import {
   useLoadAndReleaseDialogHook,
   useLoadAndReleaseDialogHook,
 } from '../../hooks/Dialog';
 } from '../../hooks/Dialog';
 import Highlighter from 'react-highlight-words';
 import Highlighter from 'react-highlight-words';
-import { parseLocationSearch } from '../../utils/Format';
 import InsertContainer from '../../components/insert/Container';
 import InsertContainer from '../../components/insert/Container';
 import ImportSample from '../../components/insert/ImportSample';
 import ImportSample from '../../components/insert/ImportSample';
 import { MilvusHttp } from '../../http/Milvus';
 import { MilvusHttp } from '../../http/Milvus';
@@ -63,16 +62,14 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
   },
 }));
 }));
 
 
-let timer: NodeJS.Timeout | null = null;
-// get init search value from url
-const { urlSearch = '' } = parseLocationSearch(window.location.search);
-
 const Collections = () => {
 const Collections = () => {
   useNavigationHook(ALL_ROUTER_TYPES.COLLECTIONS);
   useNavigationHook(ALL_ROUTER_TYPES.COLLECTIONS);
   const { handleAction } = useLoadAndReleaseDialogHook({ type: 'collection' });
   const { handleAction } = useLoadAndReleaseDialogHook({ type: 'collection' });
   const { handleInsertDialog } = useInsertDialogHook();
   const { handleInsertDialog } = useInsertDialogHook();
-
-  const [search, setSearch] = useState<string>(urlSearch);
+  const [searchParams] = useSearchParams();
+  const [search, setSearch] = useState<string>(
+    (searchParams.get('search') as string) || ''
+  );
   const [loading, setLoading] = useState<boolean>(false);
   const [loading, setLoading] = useState<boolean>(false);
   const [selectedCollections, setSelectedCollections] = useState<
   const [selectedCollections, setSelectedCollections] = useState<
     CollectionView[]
     CollectionView[]
@@ -92,13 +89,38 @@ const Collections = () => {
   const InfoIcon = icons.info;
   const InfoIcon = icons.info;
   const SourceIcon = icons.source;
   const SourceIcon = icons.source;
 
 
-  const searchedCollections = useMemo(
-    () => collections.filter(collection => collection._name.includes(search)),
-    [collections, search]
-  );
+  const fetchData = useCallback(async () => {
+    try {
+      setLoading(true);
+      const res = await CollectionHttp.getCollections();
+      const hasLoadingOrBuildingCollection = res.some(
+        v => checkLoading(v) || checkIndexBuilding(v)
+      );
+
+      // if some collection is building index or loading, start pulling data
+      if (hasLoadingOrBuildingCollection) {
+        MilvusHttp.triggerCron({
+          name: WS_EVENTS.COLLECTION,
+          type: WS_EVENTS_TYPE.START,
+        });
+      }
+
+      setCollections(res);
+    } finally {
+      setLoading(false);
+    }
+  }, [setCollections]);
+
+  useEffect(() => {
+    fetchData();
+  }, [fetchData]);
 
 
   const formatCollections = useMemo(() => {
   const formatCollections = useMemo(() => {
-    const data = searchedCollections.map(v => {
+    const filteredCollections = search
+      ? collections.filter(collection => collection._name.includes(search))
+      : collections;
+
+    const data = filteredCollections.map(v => {
       // const indexStatus = statusRes.find(item => item._name === v._name);
       // const indexStatus = statusRes.find(item => item._name === v._name);
       Object.assign(v, {
       Object.assign(v, {
         nameElement: (
         nameElement: (
@@ -117,11 +139,11 @@ const Collections = () => {
           <StatusIcon type={v._indexState || ChildrenStatusType.FINISH} />
           <StatusIcon type={v._indexState || ChildrenStatusType.FINISH} />
         ),
         ),
       });
       });
-
       return v;
       return v;
     });
     });
+
     return data;
     return data;
-  }, [classes.highlight, classes.link, search, searchedCollections]);
+  }, [search, collections]);
 
 
   const {
   const {
     pageSize,
     pageSize,
@@ -135,32 +157,6 @@ const Collections = () => {
     orderBy,
     orderBy,
   } = usePaginationHook(formatCollections);
   } = usePaginationHook(formatCollections);
 
 
-  const fetchData = useCallback(async () => {
-    try {
-      setLoading(true);
-      const res = await CollectionHttp.getCollections();
-      const hasLoadingOrBuildingCollection = res.some(
-        v => checkLoading(v) || checkIndexBuilding(v)
-      );
-
-      // if some collection is building index or loading, start pulling data
-      if (hasLoadingOrBuildingCollection) {
-        MilvusHttp.triggerCron({
-          name: WS_EVENTS.COLLECTION,
-          type: WS_EVENTS_TYPE.START,
-        });
-      }
-
-      setCollections(res);
-    } finally {
-      setLoading(false);
-    }
-  }, [setCollections]);
-
-  useEffect(() => {
-    fetchData();
-  }, [fetchData]);
-
   const handleInsert = async (
   const handleInsert = async (
     collectionName: string,
     collectionName: string,
     partitionName: string,
     partitionName: string,
@@ -271,9 +267,6 @@ const Collections = () => {
   };
   };
 
 
   const handleSearch = (value: string) => {
   const handleSearch = (value: string) => {
-    if (timer) {
-      clearTimeout(timer);
-    }
     setSearch(value);
     setSearch(value);
   };
   };
 
 

+ 4 - 4
client/src/pages/connect/AuthForm.tsx

@@ -10,7 +10,7 @@ import { useFormValidation } from '../../hooks/Form';
 import { formatForm } from '../../utils/Form';
 import { formatForm } from '../../utils/Form';
 import { MilvusHttp } from '../../http/Milvus';
 import { MilvusHttp } from '../../http/Milvus';
 import { formatAddress } from '../../utils/Format';
 import { formatAddress } from '../../utils/Format';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
 import { rootContext } from '../../context/Root';
 import { rootContext } from '../../context/Root';
 import { authContext } from '../../context/Auth';
 import { authContext } from '../../context/Auth';
 import { MILVUS_ADDRESS } from '../../consts/Localstorage';
 import { MILVUS_ADDRESS } from '../../consts/Localstorage';
@@ -42,7 +42,7 @@ const useStyles = makeStyles((theme: Theme) => ({
     width: '42px',
     width: '42px',
     height: 'auto',
     height: 'auto',
     marginBottom: '8px',
     marginBottom: '8px',
-    display: 'block'
+    display: 'block',
   },
   },
   input: {
   input: {
     margin: theme.spacing(3, 0, 0.5),
     margin: theme.spacing(3, 0, 0.5),
@@ -54,7 +54,7 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
   },
 }));
 }));
 export const AuthForm = (props: any) => {
 export const AuthForm = (props: any) => {
-  const history = useHistory();
+  const navigate = useNavigate();
   const classes = useStyles();
   const classes = useStyles();
 
 
   const { openSnackBar } = useContext(rootContext);
   const { openSnackBar } = useContext(rootContext);
@@ -161,7 +161,7 @@ export const AuthForm = (props: any) => {
 
 
       openSnackBar(successTrans('connect'));
       openSnackBar(successTrans('connect'));
       window.localStorage.setItem(MILVUS_ADDRESS, address);
       window.localStorage.setItem(MILVUS_ADDRESS, address);
-      history.push('/');
+      navigate('/');
     } catch (error: any) {
     } catch (error: any) {
       if (error?.response?.status === CODE_STATUS.UNAUTHORIZED) {
       if (error?.response?.status === CODE_STATUS.UNAUTHORIZED) {
         showAuthForm
         showAuthForm

+ 11 - 3
client/src/pages/connect/Connect.tsx

@@ -1,11 +1,19 @@
+import { useContext } from 'react';
+import { Navigate } from 'react-router-dom';
 import ConnectContainer from './ConnectContainer';
 import ConnectContainer from './ConnectContainer';
 import { AuthForm } from './AuthForm';
 import { AuthForm } from './AuthForm';
+import { authContext } from '../../context/Auth';
 
 
 const Connect = () => {
 const Connect = () => {
+  const { isAuth } = useContext(authContext);
+
   return (
   return (
-    <ConnectContainer>
-      <AuthForm />
-    </ConnectContainer>
+    <>
+      {isAuth && <Navigate to="/" replace={true} />}
+      <ConnectContainer>
+        <AuthForm />
+      </ConnectContainer>
+    </>
   );
   );
 };
 };
 
 

+ 1 - 1
client/src/pages/connect/ConnectContainer.tsx

@@ -4,7 +4,7 @@ import { ReactElement } from 'react';
 const getContainerStyles = makeStyles((theme: Theme) => ({
 const getContainerStyles = makeStyles((theme: Theme) => ({
   wrapper: {
   wrapper: {
     width: '100%',
     width: '100%',
-    height: '90%',
+    height: '100vh',
     backgroundRepeat: 'no-repeat',
     backgroundRepeat: 'no-repeat',
     backgroundSize: 'cover',
     backgroundSize: 'cover',
   },
   },

+ 135 - 0
client/src/pages/index.tsx

@@ -0,0 +1,135 @@
+import { useMemo, useContext } from 'react';
+import { Outlet, useNavigate, useLocation, Navigate } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import { makeStyles, Theme, createStyles } from '@material-ui/core';
+import GlobalEffect from '../components/layout/GlobalEffect';
+import Header from '../components/layout/Header';
+import NavMenu from '../components/menu/NavMenu';
+import { NavMenuItem } from '../components/menu/Types';
+import icons from '../components/icons/Icons';
+import { authContext } from '../context/Auth';
+import { rootContext } from '../context/Root';
+import Overview from '../pages/overview/Overview';
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    root: {
+      minHeight: '100vh',
+      backgroundColor: '#f5f5f5',
+    },
+    content: {
+      display: 'flex',
+
+      '& .active': {
+        '& path': {
+          fill: theme.palette.attuGrey.dark,
+        },
+      },
+
+      '& .normal': {
+        '& path': {
+          fill: theme.palette.primary.main,
+        },
+      },
+    },
+    body: {
+      flex: 1,
+      display: 'flex',
+      flexDirection: 'column',
+      height: '100vh',
+      overflowY: 'scroll',
+    },
+  })
+);
+
+function Index() {
+  const navigate = useNavigate();
+  const { isAuth } = useContext(authContext);
+  const { versionInfo } = useContext(rootContext);
+  const { t: navTrans } = useTranslation('nav');
+  const classes = useStyles();
+  const location = useLocation();
+  const isIndex = location.pathname === '/';
+  const defaultActive = useMemo(() => {
+    if (location.pathname.includes('collection')) {
+      return navTrans('collection');
+    }
+
+    if (location.pathname.includes('search')) {
+      return navTrans('search');
+    }
+
+    if (location.pathname.includes('system')) {
+      return navTrans('system');
+    }
+
+    if (location.pathname.includes('users')) {
+      return navTrans('user');
+    }
+
+    return navTrans('overview');
+  }, [location, navTrans]);
+
+  const menuItems: NavMenuItem[] = [
+    {
+      icon: icons.navOverview,
+      label: navTrans('overview'),
+      onClick: () => navigate('/'),
+    },
+    {
+      icon: icons.navCollection,
+      label: navTrans('collection'),
+      onClick: () => navigate('/collections'),
+    },
+    {
+      icon: icons.navSearch,
+      label: navTrans('search'),
+      onClick: () => navigate('/search'),
+      iconActiveClass: 'normal',
+      iconNormalClass: 'active',
+    },
+    {
+      icon: icons.navSystem,
+      label: navTrans('system'),
+      onClick: () => navigate('/system'),
+      iconActiveClass: 'normal',
+      iconNormalClass: 'active',
+    },
+    {
+      icon: icons.navPerson,
+      label: navTrans('user'),
+      onClick: () => navigate('/users'),
+    },
+  ];
+
+  // check if is connected
+  if (!isAuth) {
+    return <Navigate to="/connect" />;
+  }
+
+  return (
+    <>
+      <div className={classes.root}>
+        <GlobalEffect>
+          <div className={classes.content}>
+            <NavMenu
+              width="200px"
+              data={menuItems}
+              defaultActive={defaultActive}
+              // used for nested child menu
+              defaultOpen={{ [navTrans('overview')]: true }}
+              versionInfo={versionInfo}
+            />
+
+            <div className={classes.body}>
+              <Header />
+              {isIndex ? <Overview /> : <Outlet />}
+            </div>
+          </div>
+        </GlobalEffect>
+      </div>
+    </>
+  );
+}
+
+export default Index;

+ 6 - 5
client/src/pages/overview/collectionCard/CollectionCard.tsx

@@ -7,7 +7,7 @@ import CustomToolTip from '../../../components/customToolTip/CustomToolTip';
 import { CollectionCardProps } from './Types';
 import { CollectionCardProps } from './Types';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import CustomIconButton from '../../../components/customButton/CustomIconButton';
 import CustomIconButton from '../../../components/customButton/CustomIconButton';
-import { useHistory, Link } from 'react-router-dom';
+import { useNavigate, Link } from 'react-router-dom';
 import { LOADING_STATE } from '../../../consts/Milvus';
 import { LOADING_STATE } from '../../../consts/Milvus';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
@@ -84,7 +84,7 @@ const CollectionCard: FC<CollectionCardProps> = ({
     _rowCount: rowCount,
     _rowCount: rowCount,
     _loadedPercentage,
     _loadedPercentage,
   } = data;
   } = data;
-  const history = useHistory();
+  const navigate = useNavigate();
   // icons
   // icons
   const RightArrowIcon = icons.rightArrow;
   const RightArrowIcon = icons.rightArrow;
   const InfoIcon = icons.info;
   const InfoIcon = icons.info;
@@ -99,13 +99,14 @@ const CollectionCard: FC<CollectionCardProps> = ({
   };
   };
 
 
   const onVectorSearchClick = () => {
   const onVectorSearchClick = () => {
-    history.push({ pathname: '/search', search: `?collectionName=${name}` });
+    navigate({ pathname: '/search', search: `?collectionName=${name}` });
   };
   };
 
 
   return (
   return (
     <div
     <div
-      className={`card-wrapper ${classes.wrapper} ${wrapperClass} ${data._status === LOADING_STATE.LOADING && classes.loading
-        }`}
+      className={`card-wrapper ${classes.wrapper} ${wrapperClass} ${
+        data._status === LOADING_STATE.LOADING && classes.loading
+      }`}
     >
     >
       <div>
       <div>
         <Status status={status} percentage={_loadedPercentage} />
         <Status status={status} percentage={_loadedPercentage} />

+ 41 - 61
client/src/pages/partitions/Partitions.tsx

@@ -1,10 +1,7 @@
 import { makeStyles, Theme } from '@material-ui/core';
 import { makeStyles, Theme } from '@material-ui/core';
-import { FC, useCallback, useContext, useEffect, useState } from 'react';
-import {
-  PartitionManageParam,
-  // PartitionParam,
-  PartitionView,
-} from './Types';
+import { FC, useContext, useEffect, useState } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import { PartitionManageParam, PartitionView } from './Types';
 import AttuGrid from '../../components/grid/Grid';
 import AttuGrid from '../../components/grid/Grid';
 import { ColDefinitionsType, ToolBarConfig } from '../../components/grid/Types';
 import { ColDefinitionsType, ToolBarConfig } from '../../components/grid/Types';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
@@ -14,13 +11,9 @@ import CustomToolTip from '../../components/customToolTip/CustomToolTip';
 import { rootContext } from '../../context/Root';
 import { rootContext } from '../../context/Root';
 import CreatePartition from './Create';
 import CreatePartition from './Create';
 import { PartitionHttp } from '../../http/Partition';
 import { PartitionHttp } from '../../http/Partition';
-import Status from '../../components/status/Status';
 import { ManageRequestMethods } from '../../types/Common';
 import { ManageRequestMethods } from '../../types/Common';
-// import { StatusEnum } from '../../components/status/Types';
-// import { useDialogHook } from '../../hooks/Dialog';
 import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
 import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
 import Highlighter from 'react-highlight-words';
 import Highlighter from 'react-highlight-words';
-import { parseLocationSearch } from '../../utils/Format';
 import { useInsertDialogHook } from '../../hooks/Dialog';
 import { useInsertDialogHook } from '../../hooks/Dialog';
 import InsertContainer from '../../components/insert/Container';
 import InsertContainer from '../../components/insert/Container';
 import { CollectionHttp } from '../../http/Collection';
 import { CollectionHttp } from '../../http/Collection';
@@ -45,8 +38,7 @@ const useStyles = makeStyles((theme: Theme) => ({
 
 
 let timer: NodeJS.Timeout | null = null;
 let timer: NodeJS.Timeout | null = null;
 // get init search value from url
 // get init search value from url
-const { search = '' } = parseLocationSearch(window.location.search);
-
+// const { search = '' } = parseLocationSearch(window.location.search);
 const Partitions: FC<{
 const Partitions: FC<{
   collectionName: string;
   collectionName: string;
 }> = ({ collectionName }) => {
 }> = ({ collectionName }) => {
@@ -55,6 +47,10 @@ const Partitions: FC<{
   const { t: successTrans } = useTranslation('success');
   const { t: successTrans } = useTranslation('success');
   const { t: btnTrans } = useTranslation('btn');
   const { t: btnTrans } = useTranslation('btn');
   const { t: dialogTrans } = useTranslation('dialog');
   const { t: dialogTrans } = useTranslation('dialog');
+  const [searchParams] = useSearchParams();
+  const [search, setSearch] = useState<string>(
+    (searchParams.get('search') as string) || ''
+  );
   const InfoIcon = icons.info;
   const InfoIcon = icons.info;
 
 
   const { handleInsertDialog } = useInsertDialogHook();
   const { handleInsertDialog } = useInsertDialogHook();
@@ -81,35 +77,15 @@ const Partitions: FC<{
   const { setDialog, handleCloseDialog, openSnackBar } =
   const { setDialog, handleCloseDialog, openSnackBar } =
     useContext(rootContext);
     useContext(rootContext);
 
 
-  const fetchPartitions = useCallback(
-    async (collectionName: string) => {
-      try {
-        const res = await PartitionHttp.getPartitions(collectionName);
-
-        const partitions: PartitionView[] = res.map(p =>
-          Object.assign(p, {
-            _nameElement: (
-              <Highlighter
-                textToHighlight={p._formatName}
-                searchWords={[search]}
-                highlightClassName={classes.highlight}
-              />
-            ),
-            _statusElement: <Status status={p._status} />,
-          })
-        );
-        const filteredPartitions = partitions.filter(p =>
-          p._formatName.includes(search)
-        );
-        setLoading(false);
-        setPartitions(partitions);
-        setSearchedPartitions(filteredPartitions);
-      } catch (err) {
-        setLoading(false);
-      }
-    },
-    [classes.highlight]
-  );
+  const fetchPartitions = async (collectionName: string) => {
+    try {
+      const res = await PartitionHttp.getPartitions(collectionName);
+      setLoading(false);
+      setPartitions(res);
+    } catch (err) {
+      setLoading(false);
+    }
+  };
 
 
   const fetchCollectionDetail = async (name: string) => {
   const fetchCollectionDetail = async (name: string) => {
     const res = await CollectionHttp.getCollection(name);
     const res = await CollectionHttp.getCollection(name);
@@ -118,33 +94,18 @@ const Partitions: FC<{
 
 
   useEffect(() => {
   useEffect(() => {
     fetchPartitions(collectionName);
     fetchPartitions(collectionName);
-  }, [collectionName, fetchPartitions]);
-
-  const handleDelete = async () => {
-    for (const partition of selectedPartitions) {
-      const param: PartitionManageParam = {
-        partitionName: partition._name,
-        collectionName,
-        type: ManageRequestMethods.DELETE,
-      };
-      await PartitionHttp.managePartition(param);
-    }
-
-    openSnackBar(successTrans('delete', { name: t('partition') }));
-    fetchPartitions(collectionName);
-    handleCloseDialog();
-  };
+  }, [collectionName]);
 
 
-  const handleSearch = (value: string) => {
+  useEffect(() => {
     if (timer) {
     if (timer) {
       clearTimeout(timer);
       clearTimeout(timer);
     }
     }
     // add loading manually
     // add loading manually
     setLoading(true);
     setLoading(true);
     timer = setTimeout(() => {
     timer = setTimeout(() => {
-      const searchWords = [value];
-      const list = value
-        ? partitions.filter(p => p._formatName.includes(value))
+      const searchWords = [search];
+      const list = search
+        ? partitions.filter(p => p._formatName.includes(search))
         : partitions;
         : partitions;
 
 
       const highlightList = list.map(c => {
       const highlightList = list.map(c => {
@@ -162,6 +123,25 @@ const Partitions: FC<{
       setLoading(false);
       setLoading(false);
       setSearchedPartitions(highlightList);
       setSearchedPartitions(highlightList);
     }, 300);
     }, 300);
+  }, [search, partitions]);
+
+  const handleDelete = async () => {
+    for (const partition of selectedPartitions) {
+      const param: PartitionManageParam = {
+        partitionName: partition._name,
+        collectionName,
+        type: ManageRequestMethods.DELETE,
+      };
+      await PartitionHttp.managePartition(param);
+    }
+
+    openSnackBar(successTrans('delete', { name: t('partition') }));
+    fetchPartitions(collectionName);
+    handleCloseDialog();
+  };
+
+  const handleSearch = (value: string) => {
+    setSearch(value);
   };
   };
 
 
   const handleInsert = async (
   const handleInsert = async (

+ 0 - 0
client/src/plugins/search/Constants.ts → client/src/pages/search/Constants.ts


+ 0 - 0
client/src/plugins/search/SearchParams.tsx → client/src/pages/search/SearchParams.tsx


+ 0 - 0
client/src/plugins/search/Styles.ts → client/src/pages/search/Styles.ts


+ 2 - 2
client/src/plugins/search/Types.ts → client/src/pages/search/Types.ts

@@ -3,8 +3,8 @@ import { searchKeywordsType } from '../../consts/Milvus';
 import {
 import {
   DataTypeEnum,
   DataTypeEnum,
   DataTypeStringEnum,
   DataTypeStringEnum,
-} from '../../pages/collections/Types';
-import { IndexView } from '../../pages/schema/Types';
+} from '../collections/Types';
+import { IndexView } from '../schema/Types';
 
 
 export interface SearchParamsProps {
 export interface SearchParamsProps {
   // if user created index, pass metric type choosed when creating
   // if user created index, pass metric type choosed when creating

+ 1 - 1
client/src/plugins/search/VectorSearch.tsx → client/src/pages/search/VectorSearch.tsx

@@ -16,7 +16,7 @@ import SimpleMenu from '../../components/menu/SimpleMenu';
 import { TOP_K_OPTIONS } from './Constants';
 import { TOP_K_OPTIONS } from './Constants';
 import { Option } from '../../components/customSelector/Types';
 import { Option } from '../../components/customSelector/Types';
 import { CollectionHttp } from '../../http/Collection';
 import { CollectionHttp } from '../../http/Collection';
-import { CollectionData, DataTypeEnum } from '../../pages/collections/Types';
+import { CollectionData, DataTypeEnum } from '../collections/Types';
 import { IndexHttp } from '../../http/Index';
 import { IndexHttp } from '../../http/Index';
 import { getVectorSearchStyles } from './Styles';
 import { getVectorSearchStyles } from './Styles';
 import { parseValue } from '../../utils/Insert';
 import { parseValue } from '../../utils/Insert';

+ 0 - 0
client/src/plugins/system/BaseCard.tsx → client/src/pages/system/BaseCard.tsx


+ 0 - 0
client/src/plugins/system/DataCard.tsx → client/src/pages/system/DataCard.tsx


+ 0 - 0
client/src/plugins/system/LineChartCard.tsx → client/src/pages/system/LineChartCard.tsx


+ 0 - 0
client/src/plugins/system/MiniTopology.tsx → client/src/pages/system/MiniTopology.tsx


+ 0 - 0
client/src/plugins/system/NodeListView.tsx → client/src/pages/system/NodeListView.tsx


+ 0 - 0
client/src/plugins/system/Progress.tsx → client/src/pages/system/Progress.tsx


+ 0 - 0
client/src/plugins/system/ProgressCard.tsx → client/src/pages/system/ProgressCard.tsx


+ 0 - 0
client/src/plugins/system/SystemView.tsx → client/src/pages/system/SystemView.tsx


+ 0 - 0
client/src/plugins/system/Topology.tsx → client/src/pages/system/Topology.tsx


+ 0 - 0
client/src/plugins/system/Types.ts → client/src/pages/system/Types.ts


+ 0 - 13
client/src/plugins/search/config.json

@@ -1,13 +0,0 @@
-{
-  "name": "search",
-  "version": "0.1.0",
-  "client": {
-    "path": "search",
-    "entry": "VectorSearch",
-    "label": "Vector Search",
-    "iconName": "navSearch",
-    "auth": true,
-    "iconActiveClass": "activeSearchIcon",
-    "iconNormalClass": "normalSearchIcon"
-  }
-}

+ 0 - 11
client/src/plugins/system/config.json

@@ -1,11 +0,0 @@
-{
-  "name": "system-view",
-  "version": "0.1.0",
-  "client": {
-    "path": "system",
-    "entry": "SystemView",
-    "label": "System View",
-    "iconName": "navSystem",
-    "auth": true
-  }
-}

+ 0 - 58
client/src/router/Config.ts

@@ -1,58 +0,0 @@
-import Collection from '../pages/collections/Collection';
-import Collections from '../pages/collections/Collections';
-import Connect from '../pages/connect/Connect';
-import Overview from '../pages/overview/Overview';
-// import VectorSearch from '../pages/seach/VectorSearch';
-import { RouterConfigType } from './Types';
-import loadable from '@loadable/component';
-import Users from '../pages/user/User';
-
-const RouterConfig: RouterConfigType[] = [
-  {
-    path: '/',
-    component: Overview,
-    auth: true,
-  },
-  {
-    path: '/connect',
-    component: Connect,
-    auth: false,
-  },
-  {
-    path: '/collections',
-    component: Collections,
-    auth: true,
-  },
-  {
-    path: '/collections/:collectionName',
-    component: Collection,
-    auth: true,
-  },
-  { path: '/users', component: Users, auth: true },
-];
-
-async function importAll(r: any) {
-  Object.keys(r).forEach((key: any) => {
-    const content = r[key];
-    const dirName = key.split('/config.json').shift().split('/')[1];
-    const pathName = content.client?.path;
-    const fileEntry = content.client?.entry;
-    if (!pathName || !fileEntry) return;
-    const auth = content.client?.auth || false;
-    const OtherComponent = loadable(
-      () => import(`../${dirName}/${pathName}/${fileEntry}.tsx`)
-    );
-    RouterConfig.push({
-      path: `/${pathName}`,
-      component: OtherComponent,
-      auth,
-    });
-  });
-}
-
-const pluginConfigs = import.meta.glob(`../plugins/**/config.json`, {
-  eager: true,
-});
-importAll(pluginConfigs);
-
-export default RouterConfig;

+ 46 - 51
client/src/router/Router.tsx

@@ -1,54 +1,49 @@
-import { Switch, Route, Redirect, HashRouter } from 'react-router-dom';
-import routerConfig from './Config';
-import Layout from '../components/layout/Layout';
-import { useContext } from 'react';
-import { authContext } from '../context/Auth';
-/**
- * Global responsible for global effect
- * Layout responsible for ui view
- *
- */
-const RouterWrapper = () => {
-  const { isAuth } = useContext(authContext);
+import {
+  BrowserRouter,
+  Routes,
+  Route,
+  createBrowserRouter,
+  RouterProvider,
+} from 'react-router-dom';
+import Collection from '../pages/collections/Collection';
+import Collections from '../pages/collections/Collections';
+import Connect from '../pages/connect/Connect';
+import Users from '../pages/user/User';
+import Index from '../pages/index';
+import Search from '../pages/search/VectorSearch';
+import System from '../pages/system/SystemView';
 
 
-  return (
-    <HashRouter>
-      <Layout>
-        <Switch>
-          {routerConfig.map(v => (
-            <Route
-              exact
-              key={v.path}
-              path={v.path}
-              render={() => {
-                const Page = v.component;
-                return isAuth || !v.auth ? (
-                  <Page />
-                ) : (
-                  <Redirect
-                    to={{
-                      pathname: '/connect',
-                    }}
-                  />
-                );
-              }}
-            />
-          ))}
+const router = createBrowserRouter([
+  {
+    path: '/',
+    element: <Index />,
+    children: [
+      {
+        path: '/collections',
+        element: <Collections />,
+      },
+      {
+        path: '/collections/:collectionName',
+        element: <Collection />,
+      },
+      {
+        path: '/users',
+        element: <Users />,
+      },
+      {
+        path: '/search',
+        element: <Search />,
+      },
+      {
+        path: '/system',
+        element: <System />,
+      },
+    ],
+  },
+  { path: '/connect', element: <Connect /> },
+]);
 
 
-          <Route
-            render={() => {
-              return (
-                <Redirect
-                  to={{
-                    pathname: '/connect',
-                  }}
-                />
-              );
-            }}
-          ></Route>
-        </Switch>
-      </Layout>
-    </HashRouter>
-  );
+const Router = () => {
+  return <RouterProvider router={router}></RouterProvider>;
 };
 };
-export default RouterWrapper;
+export default Router;

+ 0 - 6
client/src/router/Types.ts

@@ -18,9 +18,3 @@ export type NavInfo = {
   navTitle: string;
   navTitle: string;
   backPath: string;
   backPath: string;
 };
 };
-
-export type RouterConfigType = {
-  path: string;
-  component: any;
-  auth: boolean;
-};

+ 63 - 101
client/yarn.lock

@@ -378,7 +378,7 @@
     "@babel/plugin-syntax-jsx" "^7.18.6"
     "@babel/plugin-syntax-jsx" "^7.18.6"
     "@babel/types" "^7.19.0"
     "@babel/types" "^7.19.0"
 
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7":
+"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7":
   version "7.16.0"
   version "7.16.0"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.0.tgz#e27b977f2e2088ba24748bf99b5e1dece64e4f0b"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.0.tgz#e27b977f2e2088ba24748bf99b5e1dece64e4f0b"
   integrity sha512-Nht8L0O8YCktmsDV6FqFue7vQLRx3Hb0B37lS5y0jDRqRxlBG4wIJHnf9/bgSE2UyipKFA01YtS+npRdTWBUyw==
   integrity sha512-Nht8L0O8YCktmsDV6FqFue7vQLRx3Hb0B37lS5y0jDRqRxlBG4wIJHnf9/bgSE2UyipKFA01YtS+npRdTWBUyw==
@@ -692,6 +692,11 @@
     prop-types "^15.7.2"
     prop-types "^15.7.2"
     reselect "^4.0.0"
     reselect "^4.0.0"
 
 
+"@remix-run/router@1.0.3":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.0.3.tgz#953b88c20ea00d0eddaffdc1b115c08474aa295d"
+  integrity sha512-ceuyTSs7PZ/tQqi19YZNBc5X7kj1f8p+4DIyrcIYFY9h+hd1OKm4RqtiWldR9eGEvIiJfsqwM4BsuCtRIuEw6Q==
+
 "@rollup/pluginutils@^4.1.1":
 "@rollup/pluginutils@^4.1.1":
   version "4.2.1"
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
   resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
@@ -884,6 +889,11 @@
   resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724"
   resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724"
   integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==
   integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==
 
 
+"@types/history@^4.7.11":
+  version "4.7.11"
+  resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
+  integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
+
 "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
 "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
   version "2.0.3"
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
   resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@@ -950,13 +960,20 @@
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
   integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
   integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
 
 
-"@types/react-dom@>=16.9.0", "@types/react-dom@^17.0.0":
+"@types/react-dom@>=16.9.0":
   version "17.0.11"
   version "17.0.11"
   resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
   resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
   integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
   integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
   dependencies:
   dependencies:
     "@types/react" "*"
     "@types/react" "*"
 
 
+"@types/react-dom@^18.0.8":
+  version "18.0.8"
+  resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.8.tgz#d2606d855186cd42cc1b11e63a71c39525441685"
+  integrity sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==
+  dependencies:
+    "@types/react" "*"
+
 "@types/react-highlight-words@^0.16.2":
 "@types/react-highlight-words@^0.16.2":
   version "0.16.3"
   version "0.16.3"
   resolved "https://registry.yarnpkg.com/@types/react-highlight-words/-/react-highlight-words-0.16.3.tgz#a62e8d31197a33559c38fcbe4c7c1e10d3f78cd8"
   resolved "https://registry.yarnpkg.com/@types/react-highlight-words/-/react-highlight-words-0.16.3.tgz#a62e8d31197a33559c38fcbe4c7c1e10d3f78cd8"
@@ -971,12 +988,12 @@
   dependencies:
   dependencies:
     "@types/react" "*"
     "@types/react" "*"
 
 
-"@types/react-router-dom@^5.1.7":
-  version "5.3.2"
-  resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.2.tgz#ebd8e145cf056db5c66eb1dac63c72f52e8542ee"
-  integrity sha512-ELEYRUie2czuJzaZ5+ziIp9Hhw+juEw8b7C11YNA4QdLCVbQ3qLi2l4aq8XnlqM7V31LZX8dxUuFUCrzHm6sqQ==
+"@types/react-router-dom@^5.3.3":
+  version "5.3.3"
+  resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
+  integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==
   dependencies:
   dependencies:
-    "@types/history" "*"
+    "@types/history" "^4.7.11"
     "@types/react" "*"
     "@types/react" "*"
     "@types/react-router" "*"
     "@types/react-router" "*"
 
 
@@ -1009,7 +1026,7 @@
   dependencies:
   dependencies:
     "@types/react" "*"
     "@types/react" "*"
 
 
-"@types/react@*", "@types/react@>=16.9.0", "@types/react@^17.0.0":
+"@types/react@*", "@types/react@>=16.9.0":
   version "17.0.34"
   version "17.0.34"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
   integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
   integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
@@ -1018,6 +1035,15 @@
     "@types/scheduler" "*"
     "@types/scheduler" "*"
     csstype "^3.0.2"
     csstype "^3.0.2"
 
 
+"@types/react@^18.0.25":
+  version "18.0.25"
+  resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.25.tgz#8b1dcd7e56fe7315535a4af25435e0bb55c8ae44"
+  integrity sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g==
+  dependencies:
+    "@types/prop-types" "*"
+    "@types/scheduler" "*"
+    csstype "^3.0.2"
+
 "@types/scheduler@*":
 "@types/scheduler@*":
   version "0.16.2"
   version "0.16.2"
   resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
   resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
@@ -2030,19 +2056,7 @@ highlight.js@^10.4.1, highlight.js@~10.7.0:
   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
   integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
   integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
 
 
-history@^4.9.0:
-  version "4.10.1"
-  resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
-  integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
-  dependencies:
-    "@babel/runtime" "^7.1.2"
-    loose-envify "^1.2.0"
-    resolve-pathname "^3.0.0"
-    tiny-invariant "^1.0.2"
-    tiny-warning "^1.0.0"
-    value-equal "^1.0.1"
-
-hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
+hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
   version "3.3.2"
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
   integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
   integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -2295,11 +2309,6 @@ is-weakset@^2.0.1:
     call-bind "^1.0.2"
     call-bind "^1.0.2"
     get-intrinsic "^1.1.1"
     get-intrinsic "^1.1.1"
 
 
-isarray@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
-  integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
-
 isarray@^2.0.5:
 isarray@^2.0.5:
   version "2.0.5"
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
@@ -2548,7 +2557,7 @@ lodash@^4.17.15:
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
 
-loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
+loose-envify@^1.1.0, loose-envify@^1.4.0:
   version "1.4.0"
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
   integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
   integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -2619,14 +2628,6 @@ min-indent@^1.0.0:
   resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
   resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
   integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
   integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
 
 
-mini-create-react-context@^0.4.0:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e"
-  integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==
-  dependencies:
-    "@babel/runtime" "^7.12.1"
-    tiny-warning "^1.0.3"
-
 minimatch@^3.0.4, minimatch@^3.1.1:
 minimatch@^3.0.4, minimatch@^3.1.1:
   version "3.1.2"
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -2801,13 +2802,6 @@ path-parse@^1.0.7:
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
   integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
   integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
 
 
-path-to-regexp@^1.7.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
-  integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
-  dependencies:
-    isarray "0.0.1"
-
 path-type@^4.0.0:
 path-type@^4.0.0:
   version "4.0.0"
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
@@ -2906,14 +2900,13 @@ querystringify@^2.1.1:
   resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
   resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
   integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
   integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
 
 
-react-dom@^17.0.2:
-  version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
-  integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
+react-dom@^18.2.0:
+  version "18.2.0"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
+  integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
   dependencies:
   dependencies:
     loose-envify "^1.1.0"
     loose-envify "^1.1.0"
-    object-assign "^4.1.1"
-    scheduler "^0.20.2"
+    scheduler "^0.23.0"
 
 
 react-error-boundary@^3.1.0:
 react-error-boundary@^3.1.0:
   version "3.1.4"
   version "3.1.4"
@@ -2939,7 +2932,7 @@ react-i18next@^12.0.0:
     "@babel/runtime" "^7.14.5"
     "@babel/runtime" "^7.14.5"
     html-parse-stringify "^3.0.1"
     html-parse-stringify "^3.0.1"
 
 
-react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
+react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1:
   version "16.13.1"
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -2964,34 +2957,20 @@ react-refresh@^0.14.0:
   resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
   resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
   integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
   integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
 
 
-react-router-dom@^5.2.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363"
-  integrity sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==
+react-router-dom@^6.4.3:
+  version "6.4.3"
+  resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.4.3.tgz#70093b5f65f85f1df9e5d4182eb7ff3a08299275"
+  integrity sha512-MiaYQU8CwVCaOfJdYvt84KQNjT78VF0TJrA17SIQgNHRvLnXDJO6qsFqq8F/zzB1BWZjCFIrQpu4QxcshitziQ==
   dependencies:
   dependencies:
-    "@babel/runtime" "^7.12.13"
-    history "^4.9.0"
-    loose-envify "^1.3.1"
-    prop-types "^15.6.2"
-    react-router "5.2.1"
-    tiny-invariant "^1.0.2"
-    tiny-warning "^1.0.0"
+    "@remix-run/router" "1.0.3"
+    react-router "6.4.3"
 
 
-react-router@5.2.1:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d"
-  integrity sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==
-  dependencies:
-    "@babel/runtime" "^7.12.13"
-    history "^4.9.0"
-    hoist-non-react-statics "^3.1.0"
-    loose-envify "^1.3.1"
-    mini-create-react-context "^0.4.0"
-    path-to-regexp "^1.7.0"
-    prop-types "^15.6.2"
-    react-is "^16.6.0"
-    tiny-invariant "^1.0.2"
-    tiny-warning "^1.0.0"
+react-router@6.4.3:
+  version "6.4.3"
+  resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.4.3.tgz#9ed3ee4d6e95889e9b075a5d63e29acc7def0d49"
+  integrity sha512-BT6DoGn6aV1FVP5yfODMOiieakp3z46P1Fk0RNzJMACzE7C339sFuHebfvWtnB4pzBvXXkHP2vscJzWRuUjTtA==
+  dependencies:
+    "@remix-run/router" "1.0.3"
 
 
 react-syntax-highlighter@^15.4.4:
 react-syntax-highlighter@^15.4.4:
   version "15.4.4"
   version "15.4.4"
@@ -3014,13 +2993,12 @@ react-transition-group@^4.0.0, react-transition-group@^4.4.0:
     loose-envify "^1.4.0"
     loose-envify "^1.4.0"
     prop-types "^15.6.2"
     prop-types "^15.6.2"
 
 
-react@^17.0.2:
-  version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
-  integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
+react@^18.2.0:
+  version "18.2.0"
+  resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
+  integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
   dependencies:
   dependencies:
     loose-envify "^1.1.0"
     loose-envify "^1.1.0"
-    object-assign "^4.1.1"
 
 
 redent@^3.0.0:
 redent@^3.0.0:
   version "3.0.0"
   version "3.0.0"
@@ -3078,11 +3056,6 @@ resolve-from@^4.0.0:
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
   integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
   integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
 
 
-resolve-pathname@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
-  integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
-
 resolve@^1.22.1:
 resolve@^1.22.1:
   version "1.22.1"
   version "1.22.1"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
@@ -3130,13 +3103,12 @@ saxes@^6.0.0:
   dependencies:
   dependencies:
     xmlchars "^2.2.0"
     xmlchars "^2.2.0"
 
 
-scheduler@^0.20.2:
-  version "0.20.2"
-  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
-  integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
+scheduler@^0.23.0:
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
+  integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
   dependencies:
   dependencies:
     loose-envify "^1.1.0"
     loose-envify "^1.1.0"
-    object-assign "^4.1.1"
 
 
 semver@^6.0.0, semver@^6.3.0:
 semver@^6.0.0, semver@^6.3.0:
   version "6.3.0"
   version "6.3.0"
@@ -3302,12 +3274,7 @@ test-exclude@^6.0.0:
     glob "^7.1.4"
     glob "^7.1.4"
     minimatch "^3.0.4"
     minimatch "^3.0.4"
 
 
-tiny-invariant@^1.0.2:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
-  integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
-
-tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
+tiny-warning@^1.0.2:
   version "1.0.3"
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
@@ -3403,11 +3370,6 @@ v8-to-istanbul@^9.0.0:
     "@types/istanbul-lib-coverage" "^2.0.1"
     "@types/istanbul-lib-coverage" "^2.0.1"
     convert-source-map "^1.6.0"
     convert-source-map "^1.6.0"
 
 
-value-equal@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
-  integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
-
 vite-plugin-svgr@^0.3.0:
 vite-plugin-svgr@^0.3.0:
   version "0.3.0"
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-0.3.0.tgz#c81ac7541df98d361249f9ac06b901ae22744a1b"
   resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-0.3.0.tgz#c81ac7541df98d361249f9ac06b901ae22744a1b"