Revize 6129910f
Přidáno uživatelem Václav Honzík před téměř 3 roky(ů)
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
Show dialog in login page
re #9628