Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 8c45ccb0

Přidáno uživatelem hrubyjar před asi 2 roky(ů)

Token management implemented, redirections

Zobrazit rozdíly:

Backend/Backend/Controllers/AuthController.cs
24 24
    [HttpPost("/auth/login")]
25 25
    [ProducesResponseType((int) HttpStatusCode.OK, Type = typeof(LoginResponse))]
26 26
    [ProducesResponseType((int) HttpStatusCode.Forbidden)]
27
    public ActionResult<LoginResponse> Login([FromBody] LoginRequest loginData)
27
    public ActionResult<LoginResponse> Index([FromBody] LoginRequest loginData)
28 28
    {
29 29
        try
30 30
        {
31
            var loginResponse = authService.Login(loginData.Username, loginData.Password);
31
            var loginResponse = authService.Index(loginData.Username, loginData.Password);
32 32
            if (loginResponse == null)
33 33
            {
34 34
                return Forbid();
Backend/Core/Services/AuthService/AuthService.cs
15 15
        this.jwtUtils = jwtUtils;
16 16
    }
17 17

  
18
    public LoginResponse? Login(string username, string password)
18
    public LoginResponse? Index(string username, string password)
19 19
    {
20 20
        var user = userService.CheckUsernamePassword(username, password);
21 21

  
Backend/Core/Services/AuthService/IAuthService.cs
5 5

  
6 6
public interface IAuthService
7 7
{
8
    public LoginResponse? Login(string username, string password);
8
    public LoginResponse? Index(string username, string password);
9 9
}
webapp/components/common/Auth.tsx
1
import React, { ReactNode, useContext, useEffect, useState } from 'react';
2
import { useRouter } from 'next/router';
3
import { LoggedUserContext } from '../../contexts/LoggedUserContext';
4
import { ERole } from '../../api';
5

  
6
export default function Auth({
7
    minRole,
8
    children,
9
}: {
10
    minRole?: ERole | null;
11
    children: ReactNode;
12
}) {
13
    const { role, tokenExpiration, logout } = useContext(LoggedUserContext);
14
    const [clientRender, setClientRender] = useState(false);
15
    const router = useRouter();
16

  
17
    function checkRole(): boolean {
18
        if (!minRole) {
19
            // no role required
20
            return true;
21
        }
22

  
23
        return role === minRole ?? false;
24
    }
25

  
26
    useEffect(() => {
27
        setClientRender(true);
28
    }, []);
29

  
30
    if (tokenExpiration?.isBefore(new Date())) {
31
        // token has expired
32

  
33
        (async () => {
34
            await logout();
35
            await router.push('/');
36
        })();
37
    }
38

  
39
    if (clientRender && checkRole()) {
40
        return <>{children}</>;
41
    }
42

  
43
    return null;
44
}
webapp/components/navigation/AdminNavBar.tsx
1
/**
2
 * Creates a navigation bar of an admin.
3
 * @returns Navigation bar of an admin.
4
 */
5
export function AdminNavBar() {
1
import React, { useCallback, useContext } from 'react';
2
import { Menu } from 'antd';
3
import { useRouter } from 'next/router';
4
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5
import { LoggedUserContext } from '../../contexts/LoggedUserContext';
6
import {
7
    faUser,
8
    faFileExport,
9
    faFileLines,
10
    faUsers,
11
    faTags,
12
    faBookmark,
13
} from '@fortawesome/free-solid-svg-icons';
14

  
15
const UserNavBar = () => {
16
    const router = useRouter();
17
    const { logout } = useContext(LoggedUserContext);
18
    const { SubMenu } = Menu;
19

  
20
    const handleLogout = useCallback(() => {
21
        logout();
22
        router.push('/login');
23
    }, [logout, router]);
24

  
6 25
    return (
7
        <div>
8
            <p>Navigation bar of an admin.</p>
9
        </div>
26
        <Menu theme="dark" mode="horizontal">
27
            <Menu.Item style={{}} key="1" onClick={() => router.push('/documents/admin')}>
28
                <FontAwesomeIcon icon={faFileLines} /> Dokumenty
29
            </Menu.Item>
30
            <Menu.Item style={{}} key="2" onClick={() => router.push('/users')}>
31
                <FontAwesomeIcon icon={faUsers} /> Uživatelé
32
            </Menu.Item>
33
            <Menu.Item style={{}} key="3" onClick={() => router.push('/tags')}>
34
                <FontAwesomeIcon icon={faTags} /> Značky
35
            </Menu.Item>
36
            <Menu.Item style={{}} key="4" onClick={() => router.push('/classes')}>
37
                <FontAwesomeIcon icon={faBookmark} /> Třídy
38
            </Menu.Item>
39
            <Menu.Item style={{}} key="5" onClick={() => router.push('/export')}>
40
                <FontAwesomeIcon icon={faFileExport} /> Export
41
            </Menu.Item>
42

  
43
            <SubMenu
44
                icon={<FontAwesomeIcon icon={faUser} />}
45
                key="account"
46
                title={'Admin XY'} // TODO show username
47
            >
48
                <Menu.Item key="setting:2" onClick={() => handleLogout()}>
49
                    Odhlásit se
50
                </Menu.Item>
51
            </SubMenu>
52
        </Menu>
10 53
    );
11
}
54
};
55

  
56
export default UserNavBar;
webapp/components/navigation/UserNavBar.tsx
1
/**
2
 * Creates a navigation bar of a normal user (annotator).
3
 * @returns Navigation bar of a user.
4
 */
5
export function UserNavBar() {
1
import React, { useCallback, useContext } from 'react';
2
import { Menu } from 'antd';
3
import { useRouter } from 'next/router';
4
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5
import { faFileLines, faUser } from '@fortawesome/free-solid-svg-icons';
6
import { LoggedUserContext } from '../../contexts/LoggedUserContext';
7

  
8
const UserNavBar = () => {
9
    const router = useRouter();
10
    const { logout } = useContext(LoggedUserContext);
11
    const { SubMenu } = Menu;
12

  
13
    const handleLogout = useCallback(() => {
14
        logout();
15
        router.push('/login');
16
    }, [logout, router]);
17

  
6 18
    return (
7
        <div>
8
            <p>Navigation bar of a normal user.</p>
9
        </div>
19
        <Menu theme="dark" mode="horizontal">
20
            <Menu.Item key="1" onClick={() => router.push('/documents/annotator')}>
21
                <FontAwesomeIcon icon={faFileLines} /> Dokumenty
22
            </Menu.Item>
23

  
24
            <SubMenu
25
                key="account"
26
                icon={<FontAwesomeIcon icon={faUser} />}
27
                title={'Anotátor XY'} // TODO show username
28
            >
29
                <Menu.Item key="setting:2" onClick={() => handleLogout()}>
30
                    Odhlásit se
31
                </Menu.Item>
32
            </SubMenu>
33
        </Menu>
10 34
    );
11
}
35
};
36

  
37
export default UserNavBar;
webapp/components/types/auth.ts
1
import { AppProps } from 'next/app';
2
import { ERole } from '../../api';
3

  
4
export type SecuredComponent = AppProps['Component'] & {
5
    auth: ComponentAuth;
6
};
7

  
8
export class ComponentAuth {
9
    minRole: ERole | null;
10

  
11
    constructor(minRole: ERole | null = null) {
12
        this.minRole = minRole;
13
    }
14
}
webapp/contexts/LoggedUserContext.tsx
1
import React, { createContext, useEffect, useState } from 'react';
2
import moment, { Moment } from 'moment';
3
import { getTokenData, loginUser, logoutUser } from '../utils/login';
4
import { useRouter } from 'next/router';
5
import { ERole } from '../api';
6

  
7
interface ILoggedUserProvider {
8
    login: (email: string, password: string) => Promise<boolean>;
9
    logout: () => Promise<boolean>;
10

  
11
    tokenExpiration?: Moment | null;
12
    setTokenExpiration?: (newTokenExpiration: Moment | null) => void;
13

  
14
    role?: ERole | undefined;
15
    setRole?: (newRoles: ERole | undefined) => void;
16

  
17
    refreshData?: () => Promise<void>;
18
}
19

  
20
export const LoggedUserContext = createContext<ILoggedUserProvider>({
21
    login: async (username: string, password: string): Promise<boolean> => {
22
        return false;
23
    },
24
    logout: async (): Promise<boolean> => {
25
        return false;
26
    },
27
});
28

  
29
const LoggedUserProvider = (props: { children: any }) => {
30
    const [role, setRole] = useState<ERole>();
31
    const [tokenExpiration, setTokenExpiration] = useState<Moment | null>(null);
32
    const router = useRouter();
33

  
34
    useEffect(() => {
35
        loadData();
36
    }, []);
37

  
38
    async function loadData() {
39
        const tokenData = await getTokenData();
40
        if (!tokenData) {
41
            setRole(undefined);
42
            setTokenExpiration(null);
43
        } else {
44
            setRole(tokenData.role);
45
            setTokenExpiration(moment(tokenData.expiration));
46
        }
47
    }
48

  
49
    async function login(username: string, password: string): Promise<boolean> {
50
        const res = await loginUser(username, password);
51
        if (!res) {
52
            return false;
53
        }
54

  
55
        await loadData();
56

  
57
        return true;
58
    }
59

  
60
    async function logout(): Promise<boolean> {
61
        const res = await logoutUser();
62

  
63
        if (res) {
64
            setRole(undefined);
65
        }
66
        await router.push('/');
67
        return res;
68
    }
69

  
70
    return (
71
        <LoggedUserContext.Provider
72
            value={{
73
                tokenExpiration,
74
                setTokenExpiration,
75
                role,
76
                setRole,
77
                refreshData: loadData,
78
                login,
79
                logout,
80
            }}
81
        >
82
            {props.children}
83
        </LoggedUserContext.Provider>
84
    );
85
};
86

  
87
export default LoggedUserProvider;
webapp/controllers.ts
1
import { UserApi, AuthApi, DocumentApi, AnnotationApi, TagApi } from './api';
2
import { axios } from './utils/axios';
3
import { Configuration } from './api';
4
import { getToken } from './utils/login';
5

  
6
let baseURL = 'https://localhost:7241';
7

  
8
const defaultConfig: Configuration = new Configuration({
9
    basePath: baseURL,
10
    accessToken: getToken,
11
});
12

  
13
export const authController = new AuthApi(defaultConfig, baseURL, axios);
14
export const userController = new UserApi(defaultConfig, baseURL, axios);
15
export const documentController = new DocumentApi(defaultConfig, baseURL, axios);
16
export const annotationController = new AnnotationApi(defaultConfig, baseURL, axios);
17
export const tagController = new TagApi(defaultConfig, baseURL, axios);
webapp/generate-api.sh
1
#!/bin/bash
2
rm -rf api 
3
npx @openapitools/openapi-generator-cli generate \
4
   -i http://localhost:5241/swagger/v1/swagger.json \
5
   -g typescript-axios \
6
   -o api
webapp/hooks/index.tsx
1
import useUnauthRedirect from './useUnauthRedirect';
2

  
3
export { useUnauthRedirect };
webapp/hooks/useUnauthRedirect.tsx
1
import { useRouter } from 'next/router';
2
import { useEffect } from 'react';
3
import { getToken } from '../utils/login';
4

  
5
/**
6
 * Performs redirect when user is unauthenticated.
7
 * @param path path where to redirect
8
 * @returns redirecting?
9
 */
10
const useUnauthRedirect = (path: string): boolean => {
11
    const router = useRouter();
12
    let tokenEmpty = false;
13
    useEffect(() => {
14
        async function checkToken() {
15
            const empty = (await getToken()) === '';
16
            if (tokenEmpty) {
17
                router.push(path);
18
            }
19
            // eslint-disable-next-line react-hooks/exhaustive-deps
20
            tokenEmpty = empty;
21
        }
22
        checkToken();
23
    }, [path, router]);
24

  
25
    return tokenEmpty;
26
};
27

  
28
export default useUnauthRedirect;
webapp/layouts/MainLayout.tsx
1
import { UserNavBar } from '../components/navigation/UserNavBar';
2
import { AdminNavBar } from '../components/navigation/AdminNavBar';
3 1
import 'antd/dist/antd.css';
4 2
import styles from '/styles/MainLayout.module.scss';
5
import React from 'react';
3
import { useContext } from 'react';
4
import { LoggedUserContext } from '../contexts/LoggedUserContext';
5
import AdminNavBar from '../components/navigation/AdminNavBar';
6
import UserNavBar from '../components/navigation/UserNavBar';
6 7

  
7 8
/**
8 9
 * Creates layout of main screens.
......
10 11
 * @returns The login screen.
11 12
 */
12 13
export function MainLayout(props: { children: React.ReactNode }) {
14
    const { role } = useContext(LoggedUserContext);
15
    const isAdmin = role === 'ADMINISTRATOR';
16
    const isAnnotator = role === 'ANNOTATOR';
17

  
13 18
    return (
14 19
        <div className={styles.layoutWrapper}>
15 20
            <div className={styles.header}>
16
                {/**
17
                 @todo: Select correct navigation bar
18
                 {user && <UserNavBar />}
19
                 {admin && <AdminNavBar />}
20
                **/}
21
                {(isAdmin && <AdminNavBar />) || (isAnnotator && <UserNavBar />)}
21 22
            </div>
22 23
            <main className={styles.content}>{props.children}</main>
23
            <div className={styles.footer}></div>
24
            <div className={styles.footer} />
24 25
        </div>
25 26
    );
26 27
}
webapp/package.json
6 6
    "dev": "next dev",
7 7
    "build": "next build",
8 8
    "start": "next start",
9
    "lint": "next lint"
9
    "lint": "next lint",
10
    "generate-api": "openapi-generator-cli generate -i http://localhost:5241/swagger/v1/swagger.json -g typescript-axios -o api",
11
    "postinstall": "yarn generate-api",
12
    "deduplicate": "yarn-deduplicate yarn.lock"
10 13
  },
11 14
  "dependencies": {
12
    "@fortawesome/fontawesome-free": "^6.1.0",
15
    "@fortawesome/fontawesome-svg-core": "^6.1.1",
16
    "@fortawesome/free-solid-svg-icons": "^6.1.1",
17
    "@fortawesome/react-fontawesome": "^0.1.18",
13 18
    "antd": "^4.19.5",
14 19
    "axios": "^0.26.1",
15 20
    "bootstrap": "^5.1.3",
......
17 22
    "next": "12.1.0",
18 23
    "react": "17.0.2",
19 24
    "react-bootstrap": "^2.2.1",
20
    "react-dom": "17.0.2"
25
    "react-dom": "17.0.2",
26
    "sweetalert2": "^11.4.8",
27
    "sweetalert2-react-content": "^4.2.0"
21 28
  },
22 29
  "devDependencies": {
30
    "@openapitools/openapi-generator-cli": "2.4.26",
23 31
    "@types/node": "17.0.21",
24 32
    "@types/react": "17.0.41",
25 33
    "eslint": "8.11.0",
webapp/pages/_app.tsx
1
import '../styles/globals.css'
2
import type {AppProps} from 'next/app'
1
// @ts-nocheck
2
import '../styles/globals.css';
3
import 'bootstrap/dist/css/bootstrap.css';
3 4

  
4
import 'bootstrap/dist/css/bootstrap.css'
5
import type { AppProps } from 'next/app';
6
import Head from 'next/head';
7
import { SecuredComponent } from '../components/types/auth';
8
import LoggedUserProvider from '../contexts/LoggedUserContext';
9
import Auth from '../components/common/Auth';
5 10

  
6
function MyApp({Component, pageProps}: AppProps) {
7
    return <Component {...pageProps} />
11
function MyApp({
12
    Component,
13
    pageProps,
14
}: {
15
    Component: SecuredComponent;
16
    pageProps: AppProps['pageProps'];
17
}) {
18
    return (
19
        <LoggedUserProvider>
20
            <Head>
21
                <link rel="shortcut icon" href={'/favicon.ico'} />
22
                <title>Annotation Tool</title>
23
            </Head>
24
            {Component.auth ? (
25
                <Auth minRole={Component.auth?.minRole}>
26
                    <Component {...pageProps} />
27
                </Auth>
28
            ) : (
29
                <Component {...pageProps} />
30
            )}
31
        </LoggedUserProvider>
32
    );
8 33
}
9 34

  
10
export default MyApp
35
export default MyApp;
webapp/pages/classes/index.tsx
1
import 'antd/dist/antd.css';
2
import React, { useContext, useEffect } from 'react';
3

  
4
import { useUnauthRedirect } from '../../hooks';
5
import { useRouter } from 'next/router';
6
import { Typography } from 'antd';
7
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8
import { faBookmark } from '@fortawesome/free-solid-svg-icons';
9
import { LoggedUserContext } from '../../contexts/LoggedUserContext';
10
import { MainLayout } from '../../layouts/mainLayout';
11

  
12
function ClassesPage() {
13
    const redirecting = useUnauthRedirect('/login');
14
    const { logout, role } = useContext(LoggedUserContext);
15
    const router = useRouter();
16

  
17
    useEffect(() => {
18
        if (role !== 'ADMINISTRATOR') {
19
            logout();
20
            router.push('/login');
21
        }
22

  
23
        if (!redirecting) {
24
            // TODO load classes
25
        }
26
    }, [redirecting, role, router]);
27

  
28
    return redirecting || role !== 'ADMINISTRATOR' ? null : (
29
        <MainLayout>
30
            <Typography.Title level={2}>
31
                <FontAwesomeIcon icon={faBookmark} /> Třídy
32
            </Typography.Title>
33
        </MainLayout>
34
    );
35
}
36

  
37
export default ClassesPage;
webapp/pages/documents/admin/index.tsx
1
import 'antd/dist/antd.css';
2
import React, { useContext, useEffect } from 'react';
3

  
4
import { useUnauthRedirect } from '../../../hooks';
5
import { useRouter } from 'next/router';
6
import { Button, Typography } from 'antd';
7
import { faFileLines } from '@fortawesome/free-solid-svg-icons';
8
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
9
import { LoggedUserContext } from '../../../contexts/LoggedUserContext';
10
import { MainLayout } from '../../../layouts/mainLayout';
11
import { documentController } from '../../../controllers';
12

  
13
function AdminDocumentPage() {
14
    const redirecting = useUnauthRedirect('/login');
15
    const { logout, role } = useContext(LoggedUserContext);
16
    const router = useRouter();
17
    useEffect(() => {
18
        if (role !== 'ADMINISTRATOR') {
19
            logout();
20
            router.push('/login');
21
        }
22

  
23
        if (!redirecting) {
24
            // TODO load documents
25
        }
26
    }, [logout, redirecting, role, router]);
27

  
28
    const showModal = () => {
29
        // TODO show AddDocument component
30
    };
31

  
32
    return redirecting || role !== 'ADMINISTRATOR' ? null : (
33
        <MainLayout>
34
            <Typography.Title level={2}>
35
                <FontAwesomeIcon icon={faFileLines} /> Dokumenty
36
            </Typography.Title>
37
            <Button type={'primary'} onClick={showModal}>
38
                Nahrát dokument
39
            </Button>
40
        </MainLayout>
41
    );
42
}
43

  
44
export default AdminDocumentPage;
webapp/pages/documents/annotator/index.tsx
1
import 'antd/dist/antd.css';
2
import React, { useContext, useEffect } from 'react';
3

  
4
import { useUnauthRedirect } from '../../../hooks';
5
import { useRouter } from 'next/router';
6
import { Layout, Typography } from 'antd';
7
import UserNavBar from '../../../components/navigation/userNavBar';
8
import { faFileLines } from '@fortawesome/free-solid-svg-icons';
9
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
10
import { LoggedUserContext } from '../../../contexts/LoggedUserContext';
11
import { MainLayout } from '../../../layouts/mainLayout';
12

  
13
function UserDocumentPage() {
14
    const redirecting = useUnauthRedirect('/login');
15
    const { logout, role } = useContext(LoggedUserContext);
16
    const router = useRouter();
17

  
18
    useEffect(() => {
19
        if (role !== 'ANNOTATOR') {
20
            logout();
21
            router.push('/login');
22
        }
23

  
24
        if (!redirecting) {
25
            // TODO load documents
26
        }
27
    }, [logout, redirecting, role, router]);
28

  
29
    return redirecting || role !== 'ANNOTATOR' ? null : (
30
        <MainLayout>
31
            <Typography.Title level={2}>
32
                <FontAwesomeIcon icon={faFileLines} /> Dokumenty
33
            </Typography.Title>
34
        </MainLayout>
35
    );
36
}
37

  
38
export default UserDocumentPage;
webapp/pages/export/index.tsx
1
import 'antd/dist/antd.css';
2
import React, { useContext, useEffect } from 'react';
3

  
4
import { useUnauthRedirect } from '../../hooks';
5
import { useRouter } from 'next/router';
6
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
7
import { faFileExport } from '@fortawesome/free-solid-svg-icons';
8
import { Typography } from 'antd';
9
import { LoggedUserContext } from '../../contexts/LoggedUserContext';
10
import { MainLayout } from '../../layouts/mainLayout';
11

  
12
function ExportPage() {
13
    const redirecting = useUnauthRedirect('/login');
14
    const { logout, role } = useContext(LoggedUserContext);
15
    const router = useRouter();
16

  
17
    useEffect(() => {
18
        if (role !== 'ADMINISTRATOR') {
19
            logout();
20
            router.push('/login');
21
        }
22
    }, [logout, redirecting, role, router]);
23

  
24
    return redirecting || role !== 'ADMINISTRATOR' ? null : (
25
        <MainLayout>
26
            <Typography.Title level={2}>
27
                <FontAwesomeIcon icon={faFileExport} /> Export
28
            </Typography.Title>
29
        </MainLayout>
30
    );
31
}
32

  
33
export default ExportPage;
webapp/pages/index.tsx
1 1
import type { NextPage } from 'next';
2
import Head from 'next/head';
3
import Image from 'next/image';
4
import styles from '../styles/Home.module.css';
2
import { useRouter } from 'next/router';
3
import { useContext, useEffect } from 'react';
4
import { LoggedUserContext } from '../contexts/LoggedUserContext';
5
import { getToken } from '../utils/login';
5 6

  
6 7
const Home: NextPage = () => {
7
    return (
8
        <div className={styles.container}>
9
            <Head>
10
                <title>Create Next App</title>
11
                <meta name="description" content="Generated by create next app" />
12
                <link rel="icon" href="/favicon.ico" />
13
            </Head>
14

  
15
            <main className={styles.main}>
16
                <h1 className={styles.title}>
17
                    Welcome to <a href="https://nextjs.org">Next.js!</a>
18
                </h1>
19

  
20
                <div>test {'ahoj'}</div>
21
                <p className={styles.description}>
22
                    Get started by editing{' '}
23
                    <code className={styles.code}>pages/index.tsx</code>
24
                </p>
25

  
26
                <div className={styles.grid}>
27
                    <a href="https://nextjs.org/docs" className={styles.card}>
28
                        <h2>Documentation &rarr;</h2>
29
                        <p>Find in-depth information about Next.js features and API.</p>
30
                    </a>
31

  
32
                    <a href="https://nextjs.org/learn" className={styles.card}>
33
                        <h2>Learn &rarr;</h2>
34
                        <p>Learn about Next.js in an interactive course with quizzes!</p>
35
                    </a>
36

  
37
                    <a
38
                        href="https://github.com/vercel/next.js/tree/canary/examples"
39
                        className={styles.card}
40
                    >
41
                        <h2>Examples &rarr;</h2>
42
                        <p>Discover and deploy boilerplate example Next.js projects.</p>
43
                    </a>
44

  
45
                    <a
46
                        href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
47
                        className={styles.card}
48
                    >
49
                        <h2>Deploy &rarr;</h2>
50
                        <p>
51
                            Instantly deploy your Next.js site to a public URL with
52
                            Vercel.
53
                        </p>
54
                    </a>
55
                </div>
56
            </main>
57

  
58
            <footer className={styles.footer}>
59
                <a
60
                    href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
61
                    target="_blank"
62
                    rel="noopener noreferrer"
63
                >
64
                    Powered by{' '}
65
                    <span className={styles.logo}>
66
                        <Image
67
                            src="/vercel.svg"
68
                            alt="Vercel Logo"
69
                            width={72}
70
                            height={16}
71
                        />
72
                    </span>
73
                </a>
74
            </footer>
75
        </div>
76
    );
8
    const { role } = useContext(LoggedUserContext);
9
    const router = useRouter();
10

  
11
    useEffect(() => {
12
        async function checkToken() {
13
            let token = await getToken();
14
            if ((await getToken()) === '') {
15
                router.push('/login');
16
            } else {
17
                if (role === 'ADMINISTRATOR') {
18
                    router.push('/documents/admin');
19
                } else if (role === 'ANNOTATOR') {
20
                    router.push('/documents/annotator');
21
                }
22
            }
23
        }
24

  
25
        checkToken();
26
    }, [role, router]);
27

  
28
    return null;
77 29
};
78 30

  
79 31
export default Home;
webapp/pages/login/index.tsx
1
import 'antd/dist/antd.css';
1 2
import { Form, Input, Button } from 'antd';
2 3
import { UserOutlined, LockOutlined } from '@ant-design/icons';
3
import 'antd/dist/antd.css';
4
import { LoginLayout } from '../../layouts/LoginLayout';
4
import { LoggedUserContext } from '../../contexts/LoggedUserContext';
5
import { LoginLayout } from '../../layouts/loginLayout';
6
import { useContext } from 'react';
7
import { useRouter } from 'next/router';
8
import { ShowToast } from '../../utils/alerts';
9
import { getTokenData } from '../../utils/login';
10
import { LoginResponse } from '../../api';
5 11

  
6 12
/**
7 13
 * Creates a login screen.
8 14
 * @returns Html structure of the login screen.
9 15
 */
10 16
function Login() {
17
    const { login, role } = useContext(LoggedUserContext);
18
    const router = useRouter();
11 19
    /**
12 20
     * Handles submission a form when its fields were successfully validated.
13 21
     * @param values Fields of the login form.
14 22
     */
15
    const onFinish = (values: any) => {
16
        /**
17
         @todo: delete login form log when login API is implemented
18
        **/
19
        console.log('Values of the login form: ', values);
23
    const onFinish = async (values: any) => {
24
        const loginRes = await login(values.username, values.password);
25

  
26
        if (!loginRes) {
27
            ShowToast(
28
                'Chybně zadané heslo nebo uživatelské jméno',
29
                'error',
30
                3000,
31
                'top-end'
32
            );
33
        } else {
34
            let data: LoginResponse | null = await getTokenData();
35
            ShowToast('Přihlášení proběhlo úspěšně', 'success', 3000, 'top-end');
36
            if (data?.role === 'ADMINISTRATOR') {
37
                await router.push('/documents/admin');
38
            } else if (data?.role === 'ANNOTATOR') {
39
                await router.push('/documents/annotator');
40
            }
41
        }
20 42
    };
21 43

  
22 44
    /**
......
24 46
     * @param errorInfo Validation errors.
25 47
     */
26 48
    const onFinishFailed = (errorInfo: any) => {
27
        /**
28
         @todo: delete log when error handling is implemented
29
        **/
30
        console.log('Errors: ', errorInfo);
49
        ShowToast('Zadané údaje nejsou validní', 'error', 3000, 'top-end');
31 50
    };
32 51

  
33 52
    return (
webapp/pages/tags/index.tsx
1
import 'antd/dist/antd.css';
2
import React, { useContext, useEffect } from 'react';
3

  
4
import { useUnauthRedirect } from '../../hooks';
5
import { useRouter } from 'next/router';
6
import { Typography } from 'antd';
7
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8
import { faTags } from '@fortawesome/free-solid-svg-icons';
9
import { LoggedUserContext } from '../../contexts/LoggedUserContext';
10
import { MainLayout } from '../../layouts/mainLayout';
11

  
12
function TagsPage() {
13
    const redirecting = useUnauthRedirect('/login');
14
    const { logout, role } = useContext(LoggedUserContext);
15
    const router = useRouter();
16

  
17
    useEffect(() => {
18
        if (role !== 'ADMINISTRATOR') {
19
            logout();
20
            router.push('/login');
21
        }
22
    }, [logout, redirecting, role, router]);
23

  
24
    return redirecting || role !== 'ADMINISTRATOR' ? null : (
25
        <MainLayout>
26
            <Typography.Title level={2}>
27
                <FontAwesomeIcon icon={faTags} /> Značky
28
            </Typography.Title>
29
        </MainLayout>
30
    );
31
}
32

  
33
export default TagsPage;
webapp/pages/users/index.tsx
1
import 'antd/dist/antd.css';
2
import React, { useContext, useEffect } from 'react';
3

  
4
import { useUnauthRedirect } from '../../hooks';
5
import { useRouter } from 'next/router';
6
import { Typography } from 'antd';
7
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8
import { faUsers } from '@fortawesome/free-solid-svg-icons';
9
import { LoggedUserContext } from '../../contexts/LoggedUserContext';
10
import { MainLayout } from '../../layouts/mainLayout';
11

  
12
function UsersPage() {
13
    const redirecting = useUnauthRedirect('/login');
14
    const { logout, role } = useContext(LoggedUserContext);
15
    const router = useRouter();
16

  
17
    useEffect(() => {
18
        if (role !== 'ADMINISTRATOR') {
19
            logout();
20
            router.push('/login');
21
        }
22
    }, [logout, redirecting, role, router]);
23

  
24
    return redirecting || role !== 'ADMINISTRATOR' ? null : (
25
        <MainLayout>
26
            <Typography.Title level={2}>
27
                <FontAwesomeIcon icon={faUsers} /> Uživatelé
28
            </Typography.Title>
29
        </MainLayout>
30
    );
31
}
32

  
33
export default UsersPage;
webapp/styles/MainLayout.module.scss
3 3
    display: grid;
4 4

  
5 5
    grid:
6
        [header-start] 'header' 60px [header-end]
6
        [header-start] 'header' 50px [header-end]
7 7
        [main-start] 'content' [main-end]
8 8
        [footer-start] 'footer' 30px [footer-end];
9 9

  
webapp/utils/alerts.ts
1
import Swal, { SweetAlertIcon, SweetAlertPosition } from 'sweetalert2';
2
import withReactContent from 'sweetalert2-react-content';
3

  
4
const MySwal = withReactContent(Swal);
5

  
6
export function ShowToast(
7
    text: string,
8
    icon: SweetAlertIcon = 'success',
9
    timer = 3000,
10
    position: SweetAlertPosition = 'top-right'
11
) {
12
    const Toast = Swal.mixin({
13
        toast: true,
14
        position: position,
15
        showConfirmButton: false,
16
        timer: timer,
17
        timerProgressBar: true,
18
        didOpen: (toast) => {
19
            toast.addEventListener('mouseenter', Swal.stopTimer);
20
            toast.addEventListener('mouseleave', Swal.resumeTimer);
21
        },
22
    });
23

  
24
    Toast.fire({
25
        icon: icon,
26
        title: text,
27
    });
28
}
webapp/utils/axios.ts
1
import axiosLib from 'axios';
2
import Router from 'next/router';
3
import { StatusCodes } from 'http-status-codes';
4
import { getToken } from './login';
5

  
6
export const axios = axiosLib.create({
7
    baseURL: 'https://localhost:7241',
8
    timeout: 60000,
9
});
10

  
11
// Add a request interceptor
12
axios.interceptors.request.use(async function (config) {
13
    const token = await getToken();
14
    if (token && config?.headers) {
15
        config.headers.Authorization = 'Bearer ' + token;
16
    }
17
    return config;
18
});
19

  
20
// Add a response interceptor
21
axios.interceptors.response.use(
22
    (response) => response,
23
    async function (error) {
24
        const status = error?.response?.status;
25
        if (status === StatusCodes.UNAUTHORIZED) {
26
            console.log('Unauthorized, redirecting...');
27

  
28
            if (Router.pathname !== '/') {
29
                await Router.replace({
30
                    pathname: '/',
31
                });
32
            }
33
        } else if (status === StatusCodes.FORBIDDEN) {
34
            console.log('Forbidden:', error?.response?.config?.url);
35
        }
36

  
37
        throw status;
38
    }
39
);
webapp/utils/login.ts
1
import { authController } from '../controllers';
2
import { LoginResponse } from '../api';
3

  
4
export async function loginUser(username: string, password: string): Promise<boolean> {
5
    try {
6
        const loginRes = await authController.authLoginPost({ username, password });
7

  
8
        const data: LoginResponse = loginRes.data;
9

  
10
        if (data && data.token) {
11
            localStorage.setItem('token', JSON.stringify(data));
12
            return true;
13
        }
14
    } catch (e) {
15
        if (e instanceof Error) {
16
            if (e.message.includes('status code 401')) {
17
                return false;
18
            }
19
        }
20
    }
21

  
22
    return false;
23
}
24

  
25
export async function logoutUser(): Promise<boolean> {
26
    localStorage.clear();
27
    return true;
28
}
29
export async function getTokenData(): Promise<LoginResponse | null> {
30
    const dataJSON = localStorage.getItem('token');
31

  
32
    if (!dataJSON) {
33
        // no token found
34
        return null;
35
    }
36

  
37
    return JSON.parse(dataJSON);
38
}
39
export async function getToken(): Promise<string> {
40
    const data = await getTokenData();
41
    if (!data) {
42
        return '';
43
    }
44

  
45
    return data.token ?? '';
46
}
webapp/yarn.lock
37 37
    resize-observer-polyfill "^1.5.0"
38 38

  
39 39
"@babel/runtime-corejs3@^7.10.2":
40
  version "7.17.8"
41
  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.8.tgz#d7dd49fb812f29c61c59126da3792d8740d4e284"
42
  integrity sha512-ZbYSUvoSF6dXZmMl/CYTMOvzIFnbGfv4W3SEHYgMvNsFTeLaF2gkGAF4K2ddmtSK4Emej+0aYcnSC6N5dPCXUQ==
40
  version "7.17.9"
41
  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.9.tgz#3d02d0161f0fbf3ada8e88159375af97690f4055"
42
  integrity sha512-WxYHHUWF2uZ7Hp1K+D1xQgbgkGUfA+5UPOegEXGt2Y5SMog/rYCVaifLZDbw8UkNXozEqqrZTy6bglL7xTaCOw==
43 43
  dependencies:
44 44
    core-js-pure "^3.20.2"
45 45
    regenerator-runtime "^0.13.4"
46 46

  
47
"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4":
47
"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.16", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
48 48
  version "7.17.9"
49 49
  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72"
50 50
  integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==
51 51
  dependencies:
52 52
    regenerator-runtime "^0.13.4"
53 53

  
54
"@babel/runtime@^7.10.2", "@babel/runtime@^7.13.16", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7":
55
  version "7.17.8"
56
  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2"
57
  integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==
58
  dependencies:
59
    regenerator-runtime "^0.13.4"
60

  
61 54
"@ctrl/tinycolor@^3.4.0":
62 55
  version "3.4.1"
63 56
  resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz#75b4c27948c81e88ccd3a8902047bcd797f38d32"
......
78 71
    minimatch "^3.0.4"
79 72
    strip-json-comments "^3.1.1"
80 73

  
81
"@fortawesome/fontawesome-free@^6.1.0":
82
  version "6.1.0"
83
  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.1.0.tgz#db162f63c6fdb3ecc6d44de3bbc8ed178390d709"
84
  integrity sha512-OgM74M6+Q7BuKAj8r+VfzwjnIGZrY62R4ipbiDvBW4FA0mLnB3yeuV/2bW63j+zppGyTvBp/3jqXp9ZXy43nFw==
74
"@fortawesome/fontawesome-common-types@6.1.1":
75
  version "6.1.1"
76
  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.1.tgz#7dc996042d21fc1ae850e3173b5c67b0549f9105"
77
  integrity sha512-wVn5WJPirFTnzN6tR95abCx+ocH+3IFLXAgyavnf9hUmN0CfWoDjPT/BAWsUVwSlYYVBeCLJxaqi7ZGe4uSjBA==
78

  
79
"@fortawesome/fontawesome-svg-core@^6.1.1":
80
  version "6.1.1"
81
  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.1.tgz#3424ec6182515951816be9b11665d67efdce5b5f"
82
  integrity sha512-NCg0w2YIp81f4V6cMGD9iomfsIj7GWrqmsa0ZsPh59G7PKiGN1KymZNxmF00ssuAlo/VZmpK6xazsGOwzKYUMg==
83
  dependencies:
84
    "@fortawesome/fontawesome-common-types" "6.1.1"
85

  
86
"@fortawesome/free-solid-svg-icons@^6.1.1":
87
  version "6.1.1"
88
  resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.1.1.tgz#3369e673f8fe8be2fba30b1ec274d47490a830a6"
89
  integrity sha512-0/5exxavOhI/D4Ovm2r3vxNojGZioPwmFrKg0ZUH69Q68uFhFPs6+dhAToh6VEQBntxPRYPuT5Cg1tpNa9JUPg==
90
  dependencies:
91
    "@fortawesome/fontawesome-common-types" "6.1.1"
92

  
93
"@fortawesome/react-fontawesome@^0.1.18":
94
  version "0.1.18"
95
  resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.18.tgz#dae37f718a24e14d7a99a5496c873d69af3fbd73"
96
  integrity sha512-RwLIB4TZw0M9gvy5u+TusAA0afbwM4JQIimNH/j3ygd6aIvYPQLqXMhC9ErY26J23rDPyDZldIfPq/HpTTJ/tQ==
97
  dependencies:
98
    prop-types "^15.8.1"
85 99

  
86 100
"@humanwhocodes/config-array@^0.9.2":
87 101
  version "0.9.5"
......
97 111
  resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
98 112
  integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
99 113

  
114
"@nestjs/common@8.2.6":
115
  version "8.2.6"
116
  resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-8.2.6.tgz#34cd5cc44082d3525c56c95db42ca0e5277b7d85"
117
  integrity sha512-flLYSXunxcKyjbYddrhwbc49uE705MxBt85rS3mHyhDbAIPSGGeZEqME44YyAzCg1NTfJSNe7ztmOce5kNkb9A==
118
  dependencies:
119
    axios "0.24.0"
120
    iterare "1.2.1"
121
    tslib "2.3.1"
122
    uuid "8.3.2"
123

  
124
"@nestjs/core@8.2.6":
125
  version "8.2.6"
126
  resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-8.2.6.tgz#08eb38203fb01a828227ea25972d38bfef5c818f"
127
  integrity sha512-NwPcEIMmCsucs3QaDlQvkoU1FlFM2wm/WjaqLQhkSoIEmAR1gNtBo88f5io5cpMwCo1k5xYhqGlaSl6TfngwWQ==
128
  dependencies:
129
    "@nuxtjs/opencollective" "0.3.2"
130
    fast-safe-stringify "2.1.1"
131
    iterare "1.2.1"
132
    object-hash "2.2.0"
133
    path-to-regexp "3.2.0"
134
    tslib "2.3.1"
135
    uuid "8.3.2"
136

  
100 137
"@next/env@12.1.0":
101 138
  version "12.1.0"
102 139
  resolved "https://registry.yarnpkg.com/@next/env/-/env-12.1.0.tgz#73713399399b34aa5a01771fb73272b55b22c314"
......
185 222
    "@nodelib/fs.scandir" "2.1.5"
186 223
    fastq "^1.6.0"
187 224

  
225
"@nuxtjs/opencollective@0.3.2":
226
  version "0.3.2"
227
  resolved "https://registry.yarnpkg.com/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz#620ce1044f7ac77185e825e1936115bb38e2681c"
228
  integrity sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==
229
  dependencies:
230
    chalk "^4.1.0"
231
    consola "^2.15.0"
232
    node-fetch "^2.6.1"
233

  
234
"@openapitools/openapi-generator-cli@2.4.26":
235
  version "2.4.26"
236
  resolved "https://registry.yarnpkg.com/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.4.26.tgz#67622fc41c258aeae3ff074cd92772978e03484f"
237
  integrity sha512-O42H9q1HWGoIpcpMaUu318b6bmOgcjP3MieHwOrFdoG3KyttceBGlbLf9Kbf7WM91WSNCDXum7cnEKASuoGjAg==
238
  dependencies:
239
    "@nestjs/common" "8.2.6"
240
    "@nestjs/core" "8.2.6"
241
    "@nuxtjs/opencollective" "0.3.2"
242
    chalk "4.1.2"
243
    commander "8.3.0"
244
    compare-versions "3.6.0"
245
    concurrently "6.5.1"
246
    console.table "0.10.0"
247
    fs-extra "10.0.0"
248
    glob "7.1.6"
249
    inquirer "8.2.0"
250
    lodash "4.17.21"
251
    reflect-metadata "0.1.13"
252
    rxjs "7.5.2"
253
    tslib "2.0.3"
254

  
188 255
"@popperjs/core@^2.10.1":
189
  version "2.11.4"
190
  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.4.tgz#d8c7b8db9226d2d7664553a0741ad7d0397ee503"
191
  integrity sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==
256
  version "2.11.5"
257
  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64"
258
  integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==
192 259

  
193 260
"@react-aria/ssr@^3.0.1":
194 261
  version "3.1.2"
......
197 264
  dependencies:
198 265
    "@babel/runtime" "^7.6.2"
199 266

  
200
"@restart/hooks@^0.4.0", "@restart/hooks@^0.4.5":
201
  version "0.4.5"
202
  resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.5.tgz#e7acbea237bfc9e479970500cf87538b41a1ed02"
203
  integrity sha512-tLGtY0aHeIfT7aPwUkvQuhIy3+q3w4iqmUzFLPlOAf/vNUacLaBt1j/S//jv/dQhenRh8jvswyMojCwmLvJw8A==
267
"@restart/hooks@^0.4.0", "@restart/hooks@^0.4.6":
268
  version "0.4.6"
269
  resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.6.tgz#15dcf34631a618c513efc924705c7cbe349a4a0c"
270
  integrity sha512-FzpEzy6QeLB3OpUrC9OQD/lWCluQmilLfRGa/DqbB6OmV05AEt/0Lgn3Jf6l27UIJMK0qFmNcps6p8DNLXa6Pw==
204 271
  dependencies:
205 272
    dequal "^2.0.2"
206 273

  
207
"@restart/ui@^1.0.2":
208
  version "1.1.0"
209
  resolved "https://registry.yarnpkg.com/@restart/ui/-/ui-1.1.0.tgz#46d436225162b47ecccdf191cfbcf9ec3d1d5f47"
210
  integrity sha512-sYAO1LP78Suz5cT2VEkU4U/mvdjFXNg69QHanc5OAFTWyhCBG2lFJ9FITZ7hT8P8LPqcWXcwEGzHhuxPUDDDYQ==
274
"@restart/ui@^1.2.0":
275
  version "1.2.0"
276
  resolved "https://registry.yarnpkg.com/@restart/ui/-/ui-1.2.0.tgz#fb90251aa25f99b41ccedc78a91d2a15f3c5e0fb"
277
  integrity sha512-oIh2t3tG8drZtZ9SlaV5CY6wGsUViHk8ZajjhcI+74IQHyWy+AnxDv8rJR5wVgsgcgrPBUvGNkC1AEdcGNPaLQ==
211 278
  dependencies:
212 279
    "@babel/runtime" "^7.13.16"
213 280
    "@popperjs/core" "^2.10.1"
......
216 283
    "@types/warning" "^3.0.0"
217 284
    dequal "^2.0.2"
218 285
    dom-helpers "^5.2.0"
219
    prop-types "^15.7.2"
220 286
    uncontrollable "^7.2.1"
221 287
    warning "^4.0.3"
222 288

  
223 289
"@rushstack/eslint-patch@^1.0.8":
224
  version "1.1.1"
225
  resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.1.tgz#782fa5da44c4f38ae9fd38e9184b54e451936118"
226
  integrity sha512-BUyKJGdDWqvWC5GEhyOiUrGNi9iJUr4CU0O2WxJL6QJhHeeA/NVBalH+FeK0r/x/W0rPymXt5s78TDS7d6lCwg==
290
  version "1.1.3"
291
  resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz#6801033be7ff87a6b7cadaf5b337c9f366a3c4b0"
292
  integrity sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==
293

  
294
"@types/cookie@^0.3.3":
295
  version "0.3.3"
296
  resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803"
297
  integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==
298

  
299
"@types/hoist-non-react-statics@^3.0.1":
300
  version "3.3.1"
301
  resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
302
  integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
303
  dependencies:
304
    "@types/react" "*"
305
    hoist-non-react-statics "^3.3.0"
227 306

  
228 307
"@types/invariant@^2.2.35":
229 308
  version "2.2.35"
......
241 320
  integrity sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==
242 321

  
243 322
"@types/prop-types@*", "@types/prop-types@^15.7.4":
244
  version "15.7.4"
245
  resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
246
  integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
323
  version "15.7.5"
324
  resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
325
  integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
247 326

  
248 327
"@types/react-transition-group@^4.4.4":
249 328
  version "4.4.4"
......
252 331
  dependencies:
253 332
    "@types/react" "*"
254 333

  
255
"@types/react@*", "@types/react@17.0.41", "@types/react@>=16.14.8", "@types/react@>=16.9.11":
334
"@types/react@*", "@types/react@>=16.14.8", "@types/react@>=16.9.11":
335
  version "18.0.5"
336
  resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.5.tgz#1a4d4b705ae6af5aed369dec22800b20f89f5301"
337
  integrity sha512-UPxNGInDCIKlfqBrm8LDXYWNfLHwIdisWcsH5GpMyGjhEDLFgTtlRBaoWuCua9HcyuE0rMkmAeZ3FXV1pYLIYQ==
338
  dependencies:
339
    "@types/prop-types" "*"
340
    "@types/scheduler" "*"
341
    csstype "^3.0.2"
342

  
343
"@types/react@17.0.41":
256 344
  version "17.0.41"
257 345
  resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.41.tgz#6e179590d276394de1e357b3f89d05d7d3da8b85"
258 346
  integrity sha512-chYZ9ogWUodyC7VUTRBfblysKLjnohhFY9bGLwvnUFFy48+vB9DikmB3lW0qTFmBcKSzmdglcvkHK71IioOlDA==
......
272 360
  integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=
273 361

  
274 362
"@typescript-eslint/parser@^5.0.0":
275
  version "5.15.0"
276
  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.15.0.tgz#95f603f8fe6eca7952a99bfeef9b85992972e728"
277
  integrity sha512-NGAYP/+RDM2sVfmKiKOCgJYPstAO40vPAgACoWPO/+yoYKSgAXIFaBKsV8P0Cc7fwKgvj27SjRNX4L7f4/jCKQ==
363
  version "5.19.0"
364
  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.19.0.tgz#05e587c1492868929b931afa0cb5579b0f728e75"
365
  integrity sha512-yhktJjMCJX8BSBczh1F/uY8wGRYrBeyn84kH6oyqdIJwTGKmzX5Qiq49LRQ0Jh0LXnWijEziSo6BRqny8nqLVQ==
278 366
  dependencies:
279
    "@typescript-eslint/scope-manager" "5.15.0"
280
    "@typescript-eslint/types" "5.15.0"
281
    "@typescript-eslint/typescript-estree" "5.15.0"
367
    "@typescript-eslint/scope-manager" "5.19.0"
368
    "@typescript-eslint/types" "5.19.0"
369
    "@typescript-eslint/typescript-estree" "5.19.0"
282 370
    debug "^4.3.2"
283 371

  
284
"@typescript-eslint/scope-manager@5.15.0":
285
  version "5.15.0"
286
  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.15.0.tgz#d97afab5e0abf4018d1289bd711be21676cdd0ee"
287
  integrity sha512-EFiZcSKrHh4kWk0pZaa+YNJosvKE50EnmN4IfgjkA3bTHElPtYcd2U37QQkNTqwMCS7LXeDeZzEqnsOH8chjSg==
372
"@typescript-eslint/scope-manager@5.19.0":
373
  version "5.19.0"
374
  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.19.0.tgz#97e59b0bcbcb54dbcdfba96fc103b9020bbe9cb4"
375
  integrity sha512-Fz+VrjLmwq5fbQn5W7cIJZ066HxLMKvDEmf4eu1tZ8O956aoX45jAuBB76miAECMTODyUxH61AQM7q4/GOMQ5g==
288 376
  dependencies:
289
    "@typescript-eslint/types" "5.15.0"
290
    "@typescript-eslint/visitor-keys" "5.15.0"
377
    "@typescript-eslint/types" "5.19.0"
378
    "@typescript-eslint/visitor-keys" "5.19.0"
291 379

  
292
"@typescript-eslint/types@5.15.0":
293
  version "5.15.0"
294
  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.15.0.tgz#c7bdd103843b1abae97b5518219d3e2a0d79a501"
295
  integrity sha512-yEiTN4MDy23vvsIksrShjNwQl2vl6kJeG9YkVJXjXZnkJElzVK8nfPsWKYxcsGWG8GhurYXP4/KGj3aZAxbeOA==
380
"@typescript-eslint/types@5.19.0":
381
  version "5.19.0"
382
  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.19.0.tgz#12d3d600d754259da771806ee8b2c842d3be8d12"
383
  integrity sha512-zR1ithF4Iyq1wLwkDcT+qFnhs8L5VUtjgac212ftiOP/ZZUOCuuF2DeGiZZGQXGoHA50OreZqLH5NjDcDqn34w==
296 384

  
297
"@typescript-eslint/typescript-estree@5.15.0":
298
  version "5.15.0"
299
  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.15.0.tgz#81513a742a9c657587ad1ddbca88e76c6efb0aac"
300
  integrity sha512-Hb0e3dGc35b75xLzixM3cSbG1sSbrTBQDfIScqdyvrfJZVEi4XWAT+UL/HMxEdrJNB8Yk28SKxPLtAhfCbBInA==
385
"@typescript-eslint/typescript-estree@5.19.0":
386
  version "5.19.0"
387
  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.19.0.tgz#fc987b8f62883f9ea6a5b488bdbcd20d33c0025f"
388
  integrity sha512-dRPuD4ocXdaE1BM/dNR21elSEUPKaWgowCA0bqJ6YbYkvtrPVEvZ+zqcX5a8ECYn3q5iBSSUcBBD42ubaOp0Hw==
301 389
  dependencies:
302
    "@typescript-eslint/types" "5.15.0"
303
    "@typescript-eslint/visitor-keys" "5.15.0"
390
    "@typescript-eslint/types" "5.19.0"
391
    "@typescript-eslint/visitor-keys" "5.19.0"
304 392
    debug "^4.3.2"
305 393
    globby "^11.0.4"
306 394
    is-glob "^4.0.3"
307 395
    semver "^7.3.5"
308 396
    tsutils "^3.21.0"
309 397

  
310
"@typescript-eslint/visitor-keys@5.15.0":
311
  version "5.15.0"
312
  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.15.0.tgz#5669739fbf516df060f978be6a6dce75855a8027"
313
  integrity sha512-+vX5FKtgvyHbmIJdxMJ2jKm9z2BIlXJiuewI8dsDYMp5LzPUcuTT78Ya5iwvQg3VqSVdmxyM8Anj1Jeq7733ZQ==
398
"@typescript-eslint/visitor-keys@5.19.0":
399
  version "5.19.0"
400
  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.19.0.tgz#c84ebc7f6c744707a361ca5ec7f7f64cd85b8af6"
401
  integrity sha512-Ym7zZoMDZcAKWsULi2s7UMLREdVQdScPQ/fKWMYefarCztWlHPFVJo8racf8R0Gc8FAEJ2eD4of8As1oFtnQlQ==
314 402
  dependencies:
315
    "@typescript-eslint/types" "5.15.0"
403
    "@typescript-eslint/types" "5.19.0"
316 404
    eslint-visitor-keys "^3.0.0"
317 405

  
318 406
acorn-jsx@^5.3.1:
......
335 423
    json-schema-traverse "^0.4.1"
336 424
    uri-js "^4.2.2"
337 425

  
426
ansi-escapes@^4.2.1:
427
  version "4.3.2"
428
  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
429
  integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
430
  dependencies:
431
    type-fest "^0.21.3"
432

  
338 433
ansi-regex@^5.0.1:
339 434
  version "5.0.1"
340 435
  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
341 436
  integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
342 437

  
343
ansi-styles@^4.1.0:
438
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
344 439
  version "4.3.0"
345 440
  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
346 441
  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
......
417 512
    "@babel/runtime" "^7.10.2"
418 513
    "@babel/runtime-corejs3" "^7.10.2"
419 514

  
420
array-includes@^3.1.3, array-includes@^3.1.4:
515
array-includes@^3.1.4:
421 516
  version "3.1.4"
422 517
  resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9"
423 518
  integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==
......
439 534
  integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
440 535

  
441 536
array.prototype.flat@^1.2.5:
442
  version "1.2.5"
443
  resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13"
444
  integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==
537
  version "1.3.0"
538
  resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b"
539
  integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==
445 540
  dependencies:
446 541
    call-bind "^1.0.2"
447 542
    define-properties "^1.1.3"
448
    es-abstract "^1.19.0"
543
    es-abstract "^1.19.2"
544
    es-shim-unscopables "^1.0.0"
449 545

  
450 546
array.prototype.flatmap@^1.2.5:
451
  version "1.2.5"
452
  resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz#908dc82d8a406930fdf38598d51e7411d18d4446"
453
  integrity sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==
547
  version "1.3.0"
548
  resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz#a7e8ed4225f4788a70cd910abcf0791e76a5534f"
549
  integrity sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==
454 550
  dependencies:
455
    call-bind "^1.0.0"
551
    call-bind "^1.0.2"
456 552
    define-properties "^1.1.3"
457
    es-abstract "^1.19.0"
553
    es-abstract "^1.19.2"
554
    es-shim-unscopables "^1.0.0"
458 555

  
459 556
ast-types-flow@^0.0.7:
460 557
  version "0.0.7"
......
471 568
  resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413"
472 569
  integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==
473 570

  
571
axios@0.24.0:
572
  version "0.24.0"
573
  resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6"
574
  integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==
575
  dependencies:
576
    follow-redirects "^1.14.4"
577

  
... Rozdílový soubor je zkrácen, protože jeho délka přesahuje max. limit.

Také k dispozici: Unified diff