diff --git a/.gitignore b/.gitignore index 2dcb4d9f42f23b7af576b707bd66f202bed1e826..51cc949e137154da9a41a28c542629235625a18b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ __pycache__ *.db */env *.coverage +*/coverage htmlcov .pytest_cache \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 22158c9dda3cc8462343d3ce101686335a257da2..df39c2057c01130eecf18b74915e8ddb13fb7c4c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -17,7 +17,7 @@ "type": "npm", "script": "test:coverage:html", "path": "client/", - "group": "test", + "group": "build", "problemMatcher": [], }, { diff --git a/client/package-lock.json b/client/package-lock.json index 67ccc686f7c33388184249845f2f6a1fbe71a2da..d6c2bd5b526c987718af784449b39680387b5b9b 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1753,6 +1753,14 @@ "react-transition-group": "^4.4.0" } }, + "@material-ui/icons": { + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz", + "integrity": "sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==", + "requires": { + "@babel/runtime": "^7.4.4" + } + }, "@material-ui/lab": { "version": "4.0.0-alpha.57", "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.57.tgz", diff --git a/client/package.json b/client/package.json index 4fcc3e71b81b72b12d839de649445948698e986f..4d2df9fcb423cf855346e2cfe6a3ca81057520c7 100644 --- a/client/package.json +++ b/client/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@material-ui/core": "^4.11.3", + "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.57", "@testing-library/jest-dom": "^5.11.9", "@testing-library/react": "^11.2.5", @@ -63,11 +64,12 @@ "collectCoverageFrom": [ "src/**/*.{js,jsx,tsx,ts}", "!src/index.tsx", - "!src/reportWebVitals.ts" + "!src/reportWebVitals.ts", + "!src/components/TestConnection.tsx" ], "coverageReporters": [ "text", - "cobertura", + "cobertura", "html" ] }, diff --git a/client/src/App.tsx b/client/src/App.tsx index 2df0f43f64eb8aba54fb4a1b4a081177f209c91e..1e8c051e2d67e0a74f4ef26ab737a7427cf23b52 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,8 +1,6 @@ import React from 'react' -import { BrowserRouter, Route, Switch } from 'react-router-dom' import './App.css' -import LoginForm from './components/Login' -import TestConnection from './components/TestConnection' +import Main from './Main' const App: React.FC = () => { return ( @@ -11,15 +9,7 @@ const App: React.FC = () => { rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" /> - <h1>Application</h1> - <TestConnection /> - <BrowserRouter> - <Switch> - <Route path="/"> - <LoginForm /> - </Route> - </Switch> - </BrowserRouter> + <Main /> </div> ) } diff --git a/client/src/Main.tsx b/client/src/Main.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a44422fdc2886602b51f32c09c3a11c3fca3a8b7 --- /dev/null +++ b/client/src/Main.tsx @@ -0,0 +1,17 @@ +import React from 'react' +import { BrowserRouter, Route, Switch } from 'react-router-dom' +import AdminView from './components/AdminView' +import LoginForm from './components/Login' + +const Main = () => { + return ( + <BrowserRouter> + <Switch> + <Route exact path="/" component={LoginForm} /> + <Route path="/admin" component={AdminView} /> + </Switch> + </BrowserRouter> + ) +} + +export default Main diff --git a/client/src/components/AdminView.css b/client/src/components/AdminView.css new file mode 100644 index 0000000000000000000000000000000000000000..1ad0b7ee6dd4647d7f8f7d07b6853ed5ceac4026 --- /dev/null +++ b/client/src/components/AdminView.css @@ -0,0 +1,10 @@ +.background { + background: linear-gradient(to top, #efd5ff 0%, #3d55b3 100%); + height: 100%; +} + +.top-bar { + display:flex; + justify-content: space-between; + align-items: flex-start; +} \ No newline at end of file diff --git a/client/src/components/AdminView.test.tsx b/client/src/components/AdminView.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cdc460499c777bd4ee6a456b93d751272e98ebeb --- /dev/null +++ b/client/src/components/AdminView.test.tsx @@ -0,0 +1,12 @@ +import { render } from '@testing-library/react' +import React from 'react' +import { BrowserRouter } from 'react-router-dom' +import AdminView from './AdminView' + +it('renders admin view', () => { + render( + <BrowserRouter> + <AdminView /> + </BrowserRouter> + ) +}) diff --git a/client/src/components/AdminView.tsx b/client/src/components/AdminView.tsx new file mode 100644 index 0000000000000000000000000000000000000000..54551b640789e676461065c686d424bafa290efd --- /dev/null +++ b/client/src/components/AdminView.tsx @@ -0,0 +1,139 @@ +import { + AppBar, + Button, + CssBaseline, + Divider, + Drawer, + List, + ListItem, + ListItemIcon, + ListItemText, + Toolbar, + Typography +} 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 React from 'react' +import { Link, Route, Switch, useRouteMatch } from 'react-router-dom' +import './AdminView.css' +import CompetitionManager from './CompetitionManager' +import Regions from './Regions' + +const drawerWidth = 240 +const menuItems = ['Startsida', 'Regioner', 'Användare', 'Tävlingshanterare'] + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + display: 'flex' + }, + appBar: { + width: `calc(100% - ${drawerWidth}px)`, + marginLeft: drawerWidth + }, + drawer: { + width: drawerWidth, + flexShrink: 0, + marginRight: drawerWidth + }, + drawerPaper: { + width: drawerWidth + }, + // necessary for content to be below app bar + toolbar: theme.mixins.toolbar, + content: { + flexGrow: 1, + backgroundColor: theme.palette.background.default, + paddingLeft: theme.spacing(30) + } + }) +) + +const AdminView: React.FC = (props) => { + const classes = useStyles() + const [openIndex, setOpenIndex] = React.useState(0) + const match = useRouteMatch() + console.log(match) + const { path, url } = match + return ( + <div className={classes.root}> + <CssBaseline /> + <AppBar position="fixed" className={classes.appBar}> + <Toolbar> + <Typography variant="h5" noWrap> + {menuItems[openIndex]} + </Typography> + </Toolbar> + </AppBar> + <Drawer + className={(classes.drawer, 'background')} + variant="permanent" + classes={{ + paper: classes.drawerPaper + }} + anchor="left" + > + <div className="background"> + <div className={classes.toolbar} /> + <Divider /> + <List> + {menuItems.map((text, index) => ( + <ListItem + button + component={Link} + key={text} + to={`${url}/${text.toLowerCase()}`} + selected={index === openIndex} + onClick={() => setOpenIndex(index)} + > + <ListItemIcon> + {text === 'Dashboard' ? <DashboardIcon /> : <MailIcon />} + </ListItemIcon> + <ListItemText primary={text} /> + </ListItem> + ))} + </List> + <Divider /> + <List> + <ListItem> + <Button + component={Link} + to="/" + type="submit" + fullWidth + variant="contained" + color="primary" + > + Logga ut + </Button> + </ListItem> + </List> + </div> + </Drawer> + <main className={classes.content}> + <div className={classes.toolbar} /> + <Switch> + <Route exact path={[path, `${path}/startsida`]}> + <Typography variant="h1" noWrap> + Startsida + </Typography> + </Route> + <Route path={`${path}/regioner`}> + <Regions /> + </Route> + <Route path={`${path}/användare`}> + <Typography variant="h1" noWrap> + Användare + </Typography> + </Route> + <Route path={`${path}/tävlingshanterare`}> + <CompetitionManager /> + </Route> + </Switch> + </main> + </div> + ) +} + +export default AdminView diff --git a/client/src/components/CompetitionManager.test.tsx b/client/src/components/CompetitionManager.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b7156f680329a98c468dfa3a751448191a8b088b --- /dev/null +++ b/client/src/components/CompetitionManager.test.tsx @@ -0,0 +1,7 @@ +import { render } from '@testing-library/react' +import React from 'react' +import CompetitionManager from './CompetitionManager' + +it('renders competition manager', () => { + render(<CompetitionManager />) +}) diff --git a/client/src/components/CompetitionManager.tsx b/client/src/components/CompetitionManager.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2d140fa35968cb8b227e5802b426fea7ce4bb144 --- /dev/null +++ b/client/src/components/CompetitionManager.tsx @@ -0,0 +1,12 @@ +import { Typography } from '@material-ui/core' +import React from 'react' + +const CompetitionManager: React.FC = (props) => { + return ( + <Typography variant="h1" noWrap> + Tävlingshanterare + </Typography> + ) +} + +export default CompetitionManager diff --git a/client/src/components/Login.test.tsx b/client/src/components/Login.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..12a6c79c133e96598a6e583ddcbd555e1799df97 --- /dev/null +++ b/client/src/components/Login.test.tsx @@ -0,0 +1,7 @@ +import { render } from '@testing-library/react' +import React from 'react' +import LoginForm from './Login' + +it('renders login form', () => { + render(<LoginForm />) +}) diff --git a/client/src/components/Login.tsx b/client/src/components/Login.tsx index 75123ff3b18582b09daa7305927ecf9aa6cf3568..91af9b78d06d4bdde2d1b42939bad82fe0f1c968 100644 --- a/client/src/components/Login.tsx +++ b/client/src/components/Login.tsx @@ -28,12 +28,10 @@ interface ServerResponse { const schema: Yup.SchemaOf<LoginFormModel> = Yup.object({ model: Yup.object() .shape({ - email: Yup.string() - .email('Email not valid') - .required('Email is required'), + email: Yup.string().email('Email inte giltig').required('Email krävs'), password: Yup.string() - .required('Password is required') - .min(6, 'Password must be at least 6 characters') + .required('Lösenord krävs') + .min(6, 'Lösenord måste vara minst 6 karaktärer') }) .required(), error: Yup.string().optional() @@ -69,7 +67,7 @@ const LoginForm: React.FC = (props) => { {(formik) => ( <form onSubmit={formik.handleSubmit} className="login-form"> <TextField - label="Email Address" + label="Email Adress" name="model.email" helperText={ formik.touched.model?.email ? formik.errors.model?.email : '' @@ -80,7 +78,7 @@ const LoginForm: React.FC = (props) => { margin="normal" /> <TextField - label="Password" + label="Lösenord" name="model.password" type="password" helperText={ diff --git a/client/src/components/Regions.test.tsx b/client/src/components/Regions.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6f7fb873cba29a4a56f736b0ee7d7789ba420428 --- /dev/null +++ b/client/src/components/Regions.test.tsx @@ -0,0 +1,7 @@ +import { render } from '@testing-library/react' +import React from 'react' +import Regions from './Regions' + +it('renders regions', () => { + render(<Regions />) +}) diff --git a/client/src/components/Regions.tsx b/client/src/components/Regions.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f40c834414113e7e4e4cd8f832ed37642f1c6996 --- /dev/null +++ b/client/src/components/Regions.tsx @@ -0,0 +1,12 @@ +import Typography from '@material-ui/core/Typography' +import React from 'react' + +const Regions: React.FC = (props) => { + return ( + <Typography variant="h1" noWrap> + Regions + </Typography> + ) +} + +export default Regions