Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 6129910f

Přidáno uživatelem Václav Honzík před asi 2 roky(ů)

Show dialog in login page

re #9628

Zobrazit rozdíly:

frontend/src/features/Auth/Login.tsx
1
import { Button, TextField, Typography } from '@mui/material'
2
import { useFormik } from 'formik'
3 1
import { Fragment, useEffect } from 'react'
4
import { useDispatch, useSelector } from 'react-redux'
2
import { useSelector } from 'react-redux'
5 3
import { useNavigate } from 'react-router-dom'
6
import * as yup from 'yup'
7
import { SchemaOf } from 'yup'
8 4
import { RootState } from '../redux/store'
9
import { logIn } from './userThunks'
5
import LoginDialog from './LoginDialog'
10 6

  
11
interface LoginFields {
12
    username: string
13
    password: string
14
}
15 7

  
16 8
const Login = () => {
17
    const validationSchema: SchemaOf<LoginFields> = yup.object().shape({
18
        username: yup.string().required('Username is required'),
19
        password: yup.string().required('Password is required'),
20
    })
21

  
22
    const dispatch = useDispatch()
23
    const formik = useFormik({
24
        initialValues: {
25
            username: '',
26
            password: '',
27
        },
28
        validationSchema,
29
        onSubmit: () => {
30
            dispatch(
31
                logIn({
32
                    username: formik.values.username,
33
                    password: formik.values.password,
34
                })
35
            )
36
        },
37
    })
38

  
39
    // Redirect to home if the user is logged in
40 9
    const userLoggedIn = useSelector(
41 10
        (state: RootState) => state.user.isLoggedIn
42 11
    )
12

  
13
    // Redirect to home if the user is logged in
43 14
    const navigate = useNavigate()
44 15
    useEffect(() => {
45 16
        if (userLoggedIn) {
......
49 20

  
50 21
    return (
51 22
        <Fragment>
52
            <Typography variant="h3">Login</Typography>
53

  
54
            <form onSubmit={formik.handleSubmit}>
55
                <TextField
56
                    label="Username"
57
                    name="username"
58
                    fullWidth
59
                    sx={{ mb: 2 }}
60
                    value={formik.values.username}
61
                    onChange={formik.handleChange}
62
                    error={
63
                        Boolean(formik.errors.username) &&
64
                        formik.touched.username
65
                    }
66
                    helperText={
67
                        formik.errors.username &&
68
                        formik.touched.username &&
69
                        formik.errors.username
70
                    }
71
                />
72
                <TextField
73
                    type="password"
74
                    label="Password"
75
                    name="password"
76
                    fullWidth
77
                    value={formik.values.password}
78
                    onChange={formik.handleChange}
79
                    error={
80
                        Boolean(formik.errors.password) &&
81
                        formik.touched.password
82
                    }
83
                    helperText={
84
                        formik.errors.password &&
85
                        formik.touched.password &&
86
                        formik.errors.password
87
                    }
88
                    sx={{ mb: 2 }}
89
                />
90
                <Button
91
                    size="large"
92
                    variant="contained"
93
                    color="primary"
94
                    type="submit"
95
                    fullWidth
96
                >
97
                    Login
98
                </Button>
99
            </form>
23
            <LoginDialog />
100 24
        </Fragment>
101 25
    )
102 26
}
frontend/src/features/Auth/LoginDialog.tsx
1
import { Fragment, FunctionComponent, useState } from 'react'
1
import { Fragment, FunctionComponent, useEffect, useState } from 'react'
2 2
import Dialog, { DialogProps } from '@mui/material/Dialog'
3 3
import {
4 4
    Button,
5 5
    DialogContent,
6 6
    Link,
7
    Stack,
8 7
    TextField,
9 8
    Typography,
10 9
} from '@mui/material'
11 10
import { useFormik } from 'formik'
12 11
import * as yup from 'yup'
13
import { useDispatch } from 'react-redux'
14
import { showNotification } from '../Notification/notificationSlice'
15
import axiosInstance from '../../api/api'
16
import { Link as RouterLink } from 'react-router-dom'
12
import { useDispatch, useSelector } from 'react-redux'
13
import { Link as RouterLink, useNavigate } from 'react-router-dom'
14
import { logIn } from './userThunks'
15
import { RootState } from '../redux/store'
16
import { resetLoggingIn } from './userSlice'
17 17

  
18 18
export interface CreateIndexDialogProps {
19 19
    maxWidth?: DialogProps['maxWidth']
......
22 22
const RegisterDialog: FunctionComponent<CreateIndexDialogProps> = ({
23 23
    maxWidth,
24 24
}) => {
25
    const [open, setOpen] = useState(false)
26
    const [submitButtonEnabled, setSubmitButtonEnabled] = useState(true)
25
    const [open, setOpen] = useState(true)
27 26

  
28 27
    const dispatch = useDispatch()
29

  
30
    const hideDialog = () => {
31
        setOpen(false)
32
    }
33

  
34
    const showDialog = () => {
35
        setOpen(true)
36
    }
37

  
28
    const navigate = useNavigate()
29
    dispatch(resetLoggingIn())
38 30
    const validationSchema = yup.object().shape({
39
        email: yup.string().email().required('Email is required'),
31
        username: yup.string().required('Username is required'),
40 32
        password: yup.string().required('Password is required'),
41 33
    })
42 34

  
35
    const isLoggingIn = useSelector(
36
        (state: RootState) => state.user.isLoggingIn
37
    )
38

  
43 39
    const formik = useFormik({
44 40
        initialValues: {
45
            name: '',
41
            username: '',
46 42
            password: '',
47 43
        },
48 44
        validationSchema,
49
        onSubmit: async (values) => {
50
            setSubmitButtonEnabled(false)
51
            let userRegistered = false
52
            try {
53
                const { status } = await axiosInstance.post(
54
                    `/users/${values.name}`,
55
                    values
56
                )
57

  
58
                switch (status) {
59
                    case 200:
60
                        dispatch({
61
                            message: 'User was created successfully',
62
                            severity: 'success',
63
                        })
64
                        userRegistered = true
65
                        break
66
                    case 204:
67
                        dispatch(
68
                            showNotification({
69
                                message: 'User already exists',
70
                                severity: 'error',
71
                            })
72
                        )
73
                        break
74
                    default:
75
                        dispatch({
76
                            message:
77
                                'Unknown error ocurred, the user was not registered. Please try again later',
78
                            severity: 'error',
79
                        })
80
                }
81
            } catch (err: any) {
82
                dispatch(
83
                    showNotification({
84
                        message: 'The user could not be registered 😥',
85
                        severity: 'error',
86
                    })
87
                )
88
            }
89

  
90
            if (userRegistered) {
91
                onClose()
92
            }
93

  
94
            // Always fetch new indices
95
            // TODO actually fetch the users
96
            // dispatch(fetchUsers())
97
            setSubmitButtonEnabled(true)
45
        onSubmit: () => {
46
            dispatch(
47
                logIn({
48
                    username: formik.values.username,
49
                    password: formik.values.password,
50
                })
51
            )
98 52
        },
99 53
    })
100 54

  
101
    // Method called on closing the dialog
102
    const onClose = () => {
103
        hideDialog()
55
    const onCancel = () => {
104 56
        formik.resetForm()
57
        navigate('/')
105 58
    }
106 59

  
107 60
    return (
108 61
        <Fragment>
109
            <Stack
110
                direction="row"
111
                justifyContent="flex-end"
112
                alignItems="center"
113
            >
114
                <Button variant="outlined" color="primary" onClick={showDialog}>
115
                    Create new Index
116
                </Button>
117
            </Stack>
118

  
119 62
            <Dialog
120 63
                open={open}
121 64
                fullWidth={true}
122
                onClose={onClose}
123
                maxWidth={maxWidth || 'lg'}
65
                onClose={onCancel}
66
                maxWidth="md"
124 67
            >
125 68
                <Typography sx={{ ml: 2, mt: 2 }} variant="h5" fontWeight="600">
126 69
                    Login
......
130 73
                        <TextField
131 74
                            fullWidth
132 75
                            label="Name"
133
                            name="name"
76
                            name="username"
134 77
                            sx={{ mb: 2 }}
135
                            value={formik.values.name}
78
                            value={formik.values.username}
136 79
                            onChange={formik.handleChange}
137 80
                            error={
138
                                Boolean(formik.errors.name) &&
139
                                formik.touched.name
81
                                Boolean(formik.errors.username) &&
82
                                formik.touched.username
140 83
                            }
141 84
                            helperText={
142
                                formik.errors.name &&
143
                                formik.touched.name &&
144
                                formik.errors.name
85
                                formik.errors.username &&
86
                                formik.touched.username &&
87
                                formik.errors.username
145 88
                            }
146 89
                        />
147 90
                        <TextField
......
166 109
                            <Button
167 110
                                type="submit"
168 111
                                variant="contained"
169
                                disabled={!submitButtonEnabled}
170 112
                                fullWidth
113
                                disabled={isLoggingIn}
171 114
                            >
172 115
                                Log in
173 116
                            </Button>
174 117
                        </Fragment>
175
                    </form> 
118
                    </form>
176 119

  
177
                    <Link component={RouterLink} to="/resetPassword">Forgot password?</Link>
120
                    <Link component={RouterLink} to="/resetPassword">
121
                        Forgot password?
122
                    </Link>
178 123
                </DialogContent>
179 124
            </Dialog>
180 125
        </Fragment>
frontend/src/features/Auth/RegisterDialog.tsx
1
import { useFormik } from "formik"
2

  
3
const register = () => {
4

  
5
    return <></>
6
}
frontend/src/features/Auth/userSlice.ts
8 8
    refreshToken?: string
9 9
    username: string
10 10
    roles: string[]
11
    isLoggingIn: boolean
11 12
    isLoggedIn: boolean
12 13
    lastErr?: string // consumable for errors during thunks
13 14
}
......
21 22
const initialState: UserState = {
22 23
    roles: [],
23 24
    isLoggedIn: false,
25
    isLoggingIn: false,
24 26
    username: '',
25 27
}
26 28

  
......
41 43
            ...state,
42 44
            lastErr: action.payload,
43 45
        }),
44
        setUserState: (state, action) => {
45
            return ({ ...state, ...action.payload })
46
        },
46
        setUserState: (state, action) => ({ ...state, ...action.payload }),
47
        resetLoggingIn: (state) => ({ ...state, isLoggingIn: false }),
47 48
    },
48 49

  
49 50
    // Thunks
50 51
    extraReducers: (builder) => {
51 52
        builder.addCase(logIn.fulfilled, (state, action) => {
52
            return ({ ...state, ...action.payload })
53
            return { ...state, ...action.payload }
53 54
        })
54 55
        builder.addCase(logIn.rejected, (state, action) => {
55 56
            if (action.payload && typeof action.error.message === 'string') {
56
                return ({ ...state, lastErr: action.error.message })
57
                return { ...state, lastErr: action.error.message }
57 58
            }
58 59
        })
60
        builder.addCase(logIn.pending, (state, action) => {
61
            return { ...state, isLoggingIn: true }
62
        })
59 63
    },
60 64
})
61 65

  
62

  
63 66
const userReducer = persistReducer(persistConfig, userSlice.reducer)
64 67

  
65
export const { logout, refreshTokens, setErr, setUserState } = userSlice.actions
68
export const { logout, refreshTokens, setErr, setUserState, resetLoggingIn } = userSlice.actions
66 69

  
67 70
export default userReducer
frontend/src/features/Auth/userThunks.ts
40 40
                refreshToken,
41 41
                username: sub,
42 42
                roles: authorities,
43
                isLoggingIn: false,
43 44
                isLoggedIn: true
44 45
            }
45 46
            
frontend/src/features/Navigation/navigationMenuItems.ts
15 15
    // All privileges that can access this menu item
16 16
    accessibleTo: Set<string>
17 17
    icon: OverridableComponent<SvgIconTypeMap<{}, 'svg'>>
18
    position: number
18
    position: number,
19
    isDialog?: boolean
19 20
}
20 21

  
21 22
const visitorRole = 'VISITOR'
......
69 70
        accessibleTo: new Set([visitorRoleOnly]),
70 71
        icon: LoginIcon,
71 72
        position: 5,
73
        isDialog: true
72 74
    },
73 75
    {
74 76
        name: 'Statistics',
frontend/src/features/Theme/ThemeChanger.tsx
1
export default {}

Také k dispozici: Unified diff