Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 4f42fa52

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

Login dialog + slice for notifications

re #9628

Zobrazit rozdíly:

frontend/src/App.tsx
13 13
import TrackingTool from './features/TrackingTool/TrackingTool'
14 14
import Logout from './features/Auth/Logout'
15 15
import ThemeWrapper from './features/Theme/ThemeWrapper'
16
import Notification from './features/Notification/Notification'
17
import { Fragment } from 'react'
16 18

  
17 19
const App = () => {
18

  
19 20
    return (
20 21
        <ThemeWrapper>
21
            <Navigation>
22
                <Box sx={{mx: 10}}>
23
                    <Routes>
24
                        <Route path="/" element={<Home />} />
25
                        <Route path="/catalog" element={<Catalog />} />
26
                        <Route
27
                            path="/catalog/:itemId"
28
                            element={<CatalogItemDetail />}
29
                        />
30
                        <Route path="/login" element={<Login />} />
31
                        <Route path="/logout" element={<Logout />} />
32
                        <Route path="/map" element={<TrackingTool />} />
33
                        <Route path="*" element={<NotFound />} />
34
                    </Routes>
35
                </Box>
36
            </Navigation>
22
            <Fragment>
23
                <Notification />
24
                <Navigation>
25
                    <Box sx={{ mx: 10 }}>
26
                        <Routes>
27
                            <Route path="/" element={<Home />} />
28
                            <Route path="/catalog" element={<Catalog />} />
29
                            <Route
30
                                path="/catalog/:itemId"
31
                                element={<CatalogItemDetail />}
32
                            />
33
                            <Route path="/login" element={<Login />} />
34
                            <Route path="/logout" element={<Logout />} />
35
                            <Route path="/map" element={<TrackingTool />} />
36
                            <Route path="*" element={<NotFound />} />
37
                        </Routes>
38
                    </Box>
39
                </Navigation>
40
            </Fragment>
37 41
        </ThemeWrapper>
38 42
    )
39 43
}
frontend/src/features/Auth/LoginDialog.tsx
1
import { Fragment, FunctionComponent, useState } from 'react'
2
import Dialog, { DialogProps } from '@mui/material/Dialog'
3
import {
4
    Button,
5
    DialogContent,
6
    Link,
7
    Stack,
8
    TextField,
9
    Typography,
10
} from '@mui/material'
11
import { useFormik } from 'formik'
12
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'
17

  
18
export interface CreateIndexDialogProps {
19
    maxWidth?: DialogProps['maxWidth']
20
}
21

  
22
const RegisterDialog: FunctionComponent<CreateIndexDialogProps> = ({
23
    maxWidth,
24
}) => {
25
    const [open, setOpen] = useState(false)
26
    const [submitButtonEnabled, setSubmitButtonEnabled] = useState(true)
27

  
28
    const dispatch = useDispatch()
29

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

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

  
38
    const validationSchema = yup.object().shape({
39
        email: yup.string().email().required('Email is required'),
40
        password: yup.string().required('Password is required'),
41
    })
42

  
43
    const formik = useFormik({
44
        initialValues: {
45
            name: '',
46
            password: '',
47
        },
48
        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)
98
        },
99
    })
100

  
101
    // Method called on closing the dialog
102
    const onClose = () => {
103
        hideDialog()
104
        formik.resetForm()
105
    }
106

  
107
    return (
108
        <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
            <Dialog
120
                open={open}
121
                fullWidth={true}
122
                onClose={onClose}
123
                maxWidth={maxWidth || 'lg'}
124
            >
125
                <Typography sx={{ ml: 2, mt: 2 }} variant="h5" fontWeight="600">
126
                    Login
127
                </Typography>
128
                <DialogContent>
129
                    <form onSubmit={formik.handleSubmit}>
130
                        <TextField
131
                            fullWidth
132
                            label="Name"
133
                            name="name"
134
                            sx={{ mb: 2 }}
135
                            value={formik.values.name}
136
                            onChange={formik.handleChange}
137
                            error={
138
                                Boolean(formik.errors.name) &&
139
                                formik.touched.name
140
                            }
141
                            helperText={
142
                                formik.errors.name &&
143
                                formik.touched.name &&
144
                                formik.errors.name
145
                            }
146
                        />
147
                        <TextField
148
                            fullWidth
149
                            label="Password"
150
                            name="password"
151
                            type="password"
152
                            sx={{ mb: 2 }}
153
                            value={formik.values.password}
154
                            onChange={formik.handleChange}
155
                            error={
156
                                Boolean(formik.errors.password) &&
157
                                formik.touched.password
158
                            }
159
                            helperText={
160
                                formik.errors.password &&
161
                                formik.touched.password &&
162
                                formik.errors.password
163
                            }
164
                        />
165
                        <Fragment>
166
                            <Button
167
                                type="submit"
168
                                variant="contained"
169
                                disabled={!submitButtonEnabled}
170
                                fullWidth
171
                            >
172
                                Log in
173
                            </Button>
174
                        </Fragment>
175
                    </form> 
176

  
177
                    <Link component={RouterLink} to="/resetPassword">Forgot password?</Link>
178
                </DialogContent>
179
            </Dialog>
180
        </Fragment>
181
    )
182
}
183

  
184
export default RegisterDialog
frontend/src/features/Notification/Notification.tsx
1
import { Alert, AlertColor, Snackbar } from '@mui/material'
2
import { Fragment, useEffect, useState } from 'react'
3
import { useDispatch, useSelector } from 'react-redux'
4
import { RootState } from '../redux/store'
5
import { consumeNotification } from './notificationSlice'
6

  
7
// Represents notification component that will be displayed on the screen
8
const Notification = () => {
9
    const dispatch = useDispatch()
10
    const notification = useSelector((state: RootState) => state.notification)
11

  
12
    const [displayMessage, setDisplayMessage] = useState('')
13
    const [open, setOpen] = useState(false)
14
    const [severity, setSeverity] = useState<AlertColor>('info')
15
    const [autohideDuration, setAutohideDuration] = useState<number | null>(
16
        null
17
    )
18

  
19
    const closeNotification = () => {
20
        setOpen(false)
21
        setAutohideDuration(null)
22
    }
23

  
24
    // Set the message to be displayed if something is set
25
    useEffect(() => {
26
        if (notification.message) {
27
            setDisplayMessage(notification.message)
28
            setSeverity(notification.severity as AlertColor)
29
            if (notification.autohideSecs) {
30
                setAutohideDuration(notification.autohideSecs * 1000)
31
            }
32
            // Consume the message from store
33
            dispatch(consumeNotification())
34

  
35
            // Show the message in the notification
36
            setOpen(true)
37
        }
38
    }, [notification, dispatch])
39

  
40
    return (
41
        <Fragment>
42
            <Snackbar
43
                open={open}
44
                autoHideDuration={autohideDuration}
45
                anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
46
            >
47
                <Alert severity={severity} onClose={closeNotification}>
48
                    {displayMessage}
49
                </Alert>
50
            </Snackbar>
51
        </Fragment>
52
    )
53
}
54

  
55
export default Notification
frontend/src/features/Notification/notificationSlice.ts
1
import { AlertColor } from '@mui/material'
2
import { createSlice } from '@reduxjs/toolkit'
3

  
4
export interface NotificationState {
5
    message?: string
6
    severity: AlertColor
7
    autohideSecs?: number
8
}
9

  
10
const initialState = {
11
    message: undefined,
12
    severity: 'info',
13
    autohideSecs: undefined
14
}
15

  
16
const notificationSlice = createSlice({
17
    name: 'notification',
18
    initialState,
19
    reducers: {
20
        showNotification: (state, action) => ({
21
            ...state,
22
            message: action.payload.message,
23
            severity: action.payload.severity,
24
            autohideSecs: action.payload.autohideSecs,
25
        }),
26
        // consumes the message so it is not displayed after the page gets refreshed
27
        consumeNotification: (state) => ({
28
            ...initialState,
29
        }),
30
    },
31
})
32

  
33
const notificationReducer = notificationSlice.reducer
34
export const { showNotification, consumeNotification } =
35
    notificationSlice.actions
36
export default notificationReducer
frontend/src/features/TrackingTool/TrackingTool.tsx
6 6
import TextPath from 'react-leaflet-textpath'
7 7
import PlaintextUpload from './PlaintextUpload'
8 8
import FileUpload from './FileUpload'
9
import L from 'leaflet'
10
import DeleteIcon from '@mui/icons-material/Delete'
9 11

  
10 12
// Page with tracking tool
11 13
const TrackingTool = () => {
......
64 66
                    repeat
65 67
                    center
66 68
                    weight={10}
67
                >
68
                    <Popup>Caesar 🥗 War Path (Allegedly)</Popup>
69
                </TextPath>
69
                ></TextPath>
70 70
            )
71 71
        }
72 72

  
......
123 123
                            url={mapConfig.url}
124 124
                        />
125 125
                        {coords.map(({ latitude, longitude }, idx) => (
126
                            <Marker position={[latitude, longitude]} />
126
                            <Marker
127
                                position={[latitude, longitude]}
128
                            />
127 129
                        ))}
128 130
                        {polylines}
129 131
                    </MapContainer>
130 132
                </Grid>
131
                
132 133
            </Grid>
133 134
        </Fragment>
134 135
    )
frontend/src/features/redux/store.ts
5 5
import themeReducer from '../Theme/themeSlice'
6 6
import catalogReducer from '../Catalog/catalogSlice'
7 7
import { composeWithDevTools } from 'redux-devtools-extension'
8
import notificationReducer from '../Notification/notificationSlice'
8 9

  
9 10
const composeEnhancers = composeWithDevTools({})
10 11

  
......
14 15
        user: userReducer,
15 16
        theme: themeReducer,
16 17
        catalog: catalogReducer,
18
        notification: notificationReducer
17 19
    }),
18 20
    process.env.REACT_APP_DEV_ENV === 'true'
19 21
        ? composeEnhancers( // ComposeEnhancers will inject redux-devtools-extension

Také k dispozici: Unified diff