diff --git a/client/public/favicon.ico b/client/public/favicon.ico index a11777cc471a4344702741ab1c8a588998b1311a..47e3c3fa2717eb81cf538764dce8bc4776ed61c1 100644 Binary files a/client/public/favicon.ico and b/client/public/favicon.ico differ diff --git a/client/public/index.html b/client/public/index.html index aa069f27cbd9d53394428171c3989fd03db73c76..c3e215c0d247809d37fc2a433e8ba54c2f861181 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -5,10 +5,7 @@ <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> - <meta - name="description" - content="Web site created using create-react-app" - /> + <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <!-- manifest.json provides metadata used when your web app is installed on a @@ -24,7 +21,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - <title>React App</title> + <title>Teknikåttan</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> diff --git a/client/src/pages/admin/AdminPage.tsx b/client/src/pages/admin/AdminPage.tsx index 270e2dd0c8c3ec20a57fbe628605b8ecf574d205..10e38f65bce156a82bca6512c577b8c4199d8391 100644 --- a/client/src/pages/admin/AdminPage.tsx +++ b/client/src/pages/admin/AdminPage.tsx @@ -12,7 +12,10 @@ import { } from '@material-ui/core' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import DashboardIcon from '@material-ui/icons/Dashboard' -import MailIcon from '@material-ui/icons/Mail' +import ExitToAppIcon from '@material-ui/icons/ExitToApp' +import LocationCityIcon from '@material-ui/icons/LocationCity' +import PeopleIcon from '@material-ui/icons/People' +import SettingsOverscanIcon from '@material-ui/icons/SettingsOverscan' import React from 'react' import { Link, Route, Switch, useRouteMatch } from 'react-router-dom' import { logoutUser } from '../../actions/user' @@ -21,9 +24,13 @@ import CompetitionManager from './components/CompetitionManager' import Regions from './components/Regions' import UserManager from './components/UserManager' import { LeftDrawer } from './styled' - const drawerWidth = 250 -const menuItems = ['Startsida', 'Regioner', 'Användare', 'Tävlingshanterare'] +const menuItems = [ + { text: 'Startsida', icon: DashboardIcon }, + { text: 'Regioner', icon: LocationCityIcon }, + { text: 'Användare', icon: PeopleIcon }, + { text: 'Tävlingshanterare', icon: SettingsOverscanIcon }, +] const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -61,7 +68,7 @@ const AdminView: React.FC = () => { <AppBar position="fixed" className={classes.appBar}> <Toolbar> <Typography variant="h5" noWrap> - {menuItems[openIndex]} + {menuItems[openIndex].text} </Typography> </Toolbar> </AppBar> @@ -77,24 +84,31 @@ const AdminView: React.FC = () => { <div className={classes.toolbar} /> <Divider /> <List> - {menuItems.map((text, index) => ( + {menuItems.map((value, index) => ( <ListItem button component={Link} - key={text} - to={`${url}/${text.toLowerCase()}`} + key={value.text} + to={`${url}/${value.text.toLowerCase()}`} selected={index === openIndex} onClick={() => setOpenIndex(index)} > - <ListItemIcon>{index === 0 ? <DashboardIcon /> : <MailIcon />}</ListItemIcon> - <ListItemText primary={text} /> + <ListItemIcon>{React.createElement(value.icon)}</ListItemIcon> + <ListItemText primary={value.text} /> </ListItem> ))} </List> <Divider /> <List> <ListItem> - <Button onClick={handleLogout} type="submit" fullWidth variant="contained" color="primary"> + <Button + onClick={handleLogout} + type="submit" + fullWidth + variant="contained" + color="primary" + endIcon={<ExitToAppIcon></ExitToAppIcon>} + > Logga ut </Button> </ListItem> diff --git a/client/src/pages/admin/components/AddCompetition.tsx b/client/src/pages/admin/components/AddCompetition.tsx index b67714babe4c1d57040684f1bcc1e3cfabd82836..7bc82f00bbe7a21712b5a4b3b3b0b22b17de5bda 100644 --- a/client/src/pages/admin/components/AddCompetition.tsx +++ b/client/src/pages/admin/components/AddCompetition.tsx @@ -8,8 +8,7 @@ import { getCompetitions } from '../../../actions/competitions' import { useAppDispatch, useAppSelector } from '../../../hooks' import { City } from '../../../interfaces/City' import { AddCompetitionModel } from '../../../interfaces/models' -import { AddCompetitionButton, AddCompetitionContent, AddCompetitionForm } from './styled' - +import { AddButton, AddContent, AddForm } from './styled' interface ServerResponse { code: number message: string @@ -84,9 +83,9 @@ const AddCompetition: React.FC = (props: any) => { } return ( <div> - <AddCompetitionButton color="secondary" variant="contained" onClick={handleClick}> + <AddButton color="default" variant="contained" onClick={handleClick}> Ny Tävling - </AddCompetitionButton> + </AddButton> <Popover id={id} open={open} @@ -101,14 +100,14 @@ const AddCompetition: React.FC = (props: any) => { horizontal: 'center', }} > - <AddCompetitionContent> + <AddContent> <Formik initialValues={competitionInitialValues} validationSchema={competitionSchema} onSubmit={handleCompetitionSubmit} > {(formik) => ( - <AddCompetitionForm onSubmit={formik.handleSubmit}> + <AddForm onSubmit={formik.handleSubmit}> <TextField label="Namn" name="model.name" @@ -170,10 +169,10 @@ const AddCompetition: React.FC = (props: any) => { {formik.errors.error} </Alert> )} - </AddCompetitionForm> + </AddForm> )} </Formik> - </AddCompetitionContent> + </AddContent> </Popover> </div> ) diff --git a/client/src/pages/admin/components/AddRegion.tsx b/client/src/pages/admin/components/AddRegion.tsx new file mode 100644 index 0000000000000000000000000000000000000000..658fe63fdbc5e36d3eb2f63c50e428565a89295d --- /dev/null +++ b/client/src/pages/admin/components/AddRegion.tsx @@ -0,0 +1,115 @@ +import { Button, Grid, TextField } from '@material-ui/core' +import FormControl from '@material-ui/core/FormControl' +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' +import AddIcon from '@material-ui/icons/Add' +import { Alert, AlertTitle } from '@material-ui/lab' +import axios from 'axios' +import { Formik, FormikHelpers } from 'formik' +import React from 'react' +import * as Yup from 'yup' +import { getCities } from '../../../actions/cities' +import { useAppDispatch } from '../../../hooks' +import { AddForm } from './styled' + +interface AddRegionModel { + city: '' +} +interface ServerResponse { + code: number + message: string +} +interface AddRegionFormModel { + model: AddRegionModel + error?: string +} + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + table: { + width: '100%', + }, + margin: { + margin: theme.spacing(1), + }, + button: { + width: '40px', + height: '40px', + marginTop: '20px', + }, + }) +) + +const schema: Yup.SchemaOf<AddRegionFormModel> = Yup.object({ + model: Yup.object() + .shape({ + city: Yup.string() + .required('Minst två bokstäver krävs') + .min(2) + .matches(/[a-zA-Z]/, 'Namnet får enbart innehålla a-z, A-Z.'), + }) + .required(), + error: Yup.string().optional(), +}) + +const AddRegion: React.FC = (props: any) => { + const classes = useStyles() + const dispatch = useAppDispatch() + + const handleSubmit = async (values: AddRegionFormModel, actions: FormikHelpers<AddRegionFormModel>) => { + const params = { + name: values.model.city, + } + await axios + .post<ServerResponse>('/misc/cities', params) + .then(() => { + actions.resetForm() + dispatch(getCities()) + }) + .catch(({ response }) => { + console.warn(response.data) + if (response.data && response.data.message) + actions.setFieldError('error', response.data && response.data.message) + else actions.setFieldError('error', 'Something went wrong, please try again') + }) + .finally(() => { + actions.setSubmitting(false) + }) + } + + const initValues: AddRegionFormModel = { + model: { city: '' }, + } + + return ( + <Formik initialValues={initValues} validationSchema={schema} onSubmit={handleSubmit}> + {(formik) => ( + <AddForm onSubmit={formik.handleSubmit}> + <FormControl className={classes.margin}> + <Grid container={true}> + <TextField + className={classes.margin} + helperText={formik.touched.model?.city ? formik.errors.model?.city : ''} + error={Boolean(formik.touched.model?.city && formik.errors.model?.city)} + onChange={formik.handleChange} + onBlur={formik.handleBlur} + name="model.city" + label="Region" + ></TextField> + <Button className={classes.button} color="default" variant="contained" type="submit"> + <AddIcon></AddIcon> + </Button> + </Grid> + </FormControl> + {formik.errors.error && ( + <Alert severity="error"> + <AlertTitle>Error</AlertTitle> + {formik.errors.error} + </Alert> + )} + </AddForm> + )} + </Formik> + ) +} + +export default AddRegion diff --git a/client/src/pages/admin/components/AddUser.tsx b/client/src/pages/admin/components/AddUser.tsx index 81c6adc96b26e230acf1ee2b73b68b28c293d7be..f55caa3a83b552536c78d3741fbe5e85bb603707 100644 --- a/client/src/pages/admin/components/AddUser.tsx +++ b/client/src/pages/admin/components/AddUser.tsx @@ -1,4 +1,5 @@ import { Button, FormControl, InputLabel, MenuItem, Popover, TextField } from '@material-ui/core' +import PersonAddIcon from '@material-ui/icons/PersonAdd' import { Alert, AlertTitle } from '@material-ui/lab' import axios from 'axios' import { Formik, FormikHelpers } from 'formik' @@ -9,8 +10,7 @@ import { useAppDispatch, useAppSelector } from '../../../hooks' import { City } from '../../../interfaces/City' import { AddUserModel } from '../../../interfaces/models' import { Role } from '../../../interfaces/Role' -import { AddCompetitionButton, AddCompetitionContent, AddCompetitionForm } from './styled' - +import { AddButton, AddContent, AddForm } from './styled' interface ServerResponse { code: number message: string @@ -91,9 +91,9 @@ const AddUser: React.FC = (props: any) => { } return ( <div> - <AddCompetitionButton color="secondary" variant="contained" onClick={handleClick}> + <AddButton color="default" variant="contained" onClick={handleClick} endIcon={<PersonAddIcon></PersonAddIcon>}> Ny Användare - </AddCompetitionButton> + </AddButton> <Popover id={id} open={open} @@ -108,10 +108,10 @@ const AddUser: React.FC = (props: any) => { horizontal: 'center', }} > - <AddCompetitionContent> + <AddContent> <Formik initialValues={userInitialValues} validationSchema={userSchema} onSubmit={handleCompetitionSubmit}> {(formik) => ( - <AddCompetitionForm onSubmit={formik.handleSubmit}> + <AddForm onSubmit={formik.handleSubmit}> <TextField label="Email" name="model.email" @@ -208,10 +208,10 @@ const AddUser: React.FC = (props: any) => { {formik.errors.error} </Alert> )} - </AddCompetitionForm> + </AddForm> )} </Formik> - </AddCompetitionContent> + </AddContent> </Popover> </div> ) diff --git a/client/src/pages/admin/components/CompetitionManager.tsx b/client/src/pages/admin/components/CompetitionManager.tsx index 2acce64b8b11b33e089f15f8d34b1472de2b8898..f41c17058f1f69d37aadb29b323496bf048b389a 100644 --- a/client/src/pages/admin/components/CompetitionManager.tsx +++ b/client/src/pages/admin/components/CompetitionManager.tsx @@ -20,7 +20,7 @@ import { getCompetitions, setFilterParams } from '../../../actions/competitions' import { useAppDispatch, useAppSelector } from '../../../hooks' import { CompetitionFilterParams } from '../../../interfaces/CompetitionFilterParams' import AddCompetition from './AddCompetition' -import { FilterContainer, RemoveCompetition, TopBar, YearFilterTextField } from './styled' +import { FilterContainer, RemoveMenuItem, TopBar, YearFilterTextField } from './styled' const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -178,7 +178,7 @@ const CompetitionManager: React.FC = (props: any) => { <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}> <MenuItem onClick={handleClose}>Starta</MenuItem> <MenuItem onClick={handleClose}>Duplicera</MenuItem> - <RemoveCompetition onClick={handleDeleteCompetition}>Ta bort</RemoveCompetition> + <RemoveMenuItem onClick={handleDeleteCompetition}>Ta bort</RemoveMenuItem> </Menu> </div> ) diff --git a/client/src/pages/admin/components/Regions.tsx b/client/src/pages/admin/components/Regions.tsx index 103c2b1d820643a3fe80b2a2e241e42505eb6e5d..7f4ece95e14ed1a661c869e2f9c6824344f61ec5 100644 --- a/client/src/pages/admin/components/Regions.tsx +++ b/client/src/pages/admin/components/Regions.tsx @@ -1,5 +1,4 @@ -import { Button, Menu, TextField, Typography } from '@material-ui/core' -import FormControl from '@material-ui/core/FormControl' +import { Button, Menu, Typography } from '@material-ui/core' import Paper from '@material-ui/core/Paper' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import Table from '@material-ui/core/Table' @@ -13,8 +12,8 @@ import axios from 'axios' import React, { useEffect } from 'react' import { getCities } from '../../../actions/cities' import { useAppDispatch, useAppSelector } from '../../../hooks' -import { RemoveCompetition, TopBar } from './styled' - +import AddRegion from './AddRegion' +import { RemoveMenuItem, TopBar } from './styled' const useStyles = makeStyles((theme: Theme) => createStyles({ table: { @@ -31,7 +30,7 @@ const UserManager: React.FC = (props: any) => { const [activeId, setActiveId] = React.useState<number | undefined>(undefined) const citiesTotal = useAppSelector((state) => state.cities.total) const cities = useAppSelector((state) => state.cities.cities) - const [newCity, setNewCity] = React.useState() + const [newCity, setNewCity] = React.useState<string>() const classes = useStyles() const dispatch = useAppDispatch() const handleClose = () => { @@ -81,12 +80,7 @@ const UserManager: React.FC = (props: any) => { return ( <div> <TopBar> - <FormControl className={classes.margin}> - <TextField className={classes.margin} value={newCity} onChange={handleChange} label="Region"></TextField> - <Button color="primary" variant="contained" onClick={handleAddCity}> - Lägg till - </Button> - </FormControl> + <AddRegion></AddRegion> </TopBar> <TableContainer component={Paper}> <Table className={classes.table} aria-label="simple table"> @@ -113,7 +107,7 @@ const UserManager: React.FC = (props: any) => { {(!cities || cities.length === 0) && <Typography>Inga regioner hittades</Typography>} </TableContainer> <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}> - <RemoveCompetition onClick={handleDeleteCity}>Ta bort</RemoveCompetition> + <RemoveMenuItem onClick={handleDeleteCity}>Ta bort</RemoveMenuItem> </Menu> </div> ) diff --git a/client/src/pages/admin/components/UserManager.tsx b/client/src/pages/admin/components/UserManager.tsx index 7bea8713a8bda1c54ac7259cdd0915fc73bc86d9..d85290b2305808cfd35063d3ed7d403b19b57b18 100644 --- a/client/src/pages/admin/components/UserManager.tsx +++ b/client/src/pages/admin/components/UserManager.tsx @@ -20,7 +20,7 @@ import { getSearchUsers, setFilterParams } from '../../../actions/searchUser' import { useAppDispatch, useAppSelector } from '../../../hooks' import { UserFilterParams } from '../../../interfaces/UserData' import AddUser from './AddUser' -import { FilterContainer, RemoveCompetition, TopBar } from './styled' +import { FilterContainer, RemoveMenuItem, TopBar } from './styled' const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -192,7 +192,7 @@ const UserManager: React.FC = (props: any) => { <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}> <MenuItem>Redigera</MenuItem> <MenuItem>Byt lösenord</MenuItem> - <RemoveCompetition onClick={handleDeleteUsers}>Ta bort</RemoveCompetition> + <RemoveMenuItem onClick={handleDeleteUsers}>Ta bort</RemoveMenuItem> </Menu> </div> ) diff --git a/client/src/pages/admin/components/styled.tsx b/client/src/pages/admin/components/styled.tsx index 69040178507526760405089015c6928652cf7897..985ed510c666594204c0370314da79955d9b514b 100644 --- a/client/src/pages/admin/components/styled.tsx +++ b/client/src/pages/admin/components/styled.tsx @@ -7,20 +7,20 @@ export const TopBar = styled.div` align-items: flex-end; ` -export const AddCompetitionButton = styled(Button)` +export const AddButton = styled(Button)` margin-bottom: 8px; ` -export const AddCompetitionForm = styled.form` +export const AddForm = styled.form` display: flex; flex-direction: column; ` -export const AddCompetitionContent = styled.div` +export const AddContent = styled.div` padding: 15px; ` -export const RemoveCompetition = styled(MenuItem)` +export const RemoveMenuItem = styled(MenuItem)` color: red; `