From 1585b87a1d9cc91bb6f0a036f2ca3ee6d21ffac1 Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Tue, 2 Mar 2021 15:05:51 +0100
Subject: [PATCH 01/10] #21: Added competition login

---
 client/src/components/Login.css |   3 +-
 client/src/components/Login.tsx | 150 +++++++++++++++++++++++++++-----
 client/src/interfaces/models.ts |   6 +-
 3 files changed, 134 insertions(+), 25 deletions(-)

diff --git a/client/src/components/Login.css b/client/src/components/Login.css
index 5a01047d..abefd48f 100644
--- a/client/src/components/Login.css
+++ b/client/src/components/Login.css
@@ -1,10 +1,11 @@
 .login-page {
   display: flex;
+  height: 100%;
   justify-content: center;
+  align-items: center;
 }
 
 .login-form {
   display: flex;
   flex-direction: column;
-  width: 250px;
 }
diff --git a/client/src/components/Login.tsx b/client/src/components/Login.tsx
index 91af9b78..5df0e3d0 100644
--- a/client/src/components/Login.tsx
+++ b/client/src/components/Login.tsx
@@ -1,22 +1,22 @@
-import { Button, TextField } from '@material-ui/core'
-import { withStyles } from '@material-ui/core/styles'
+import { AppBar, Button, Tab, Tabs, TextField } from '@material-ui/core'
+import { makeStyles, Theme, withStyles } from '@material-ui/core/styles'
 import { Alert, AlertTitle } from '@material-ui/lab'
 import axios from 'axios'
 import { Formik, FormikHelpers } from 'formik'
-import React, { useState } from 'react'
+import React from 'react'
 import * as Yup from 'yup'
-import { LoginModel } from '../interfaces/models'
+import { AccountLoginModel, CompetitionLoginModel } from '../interfaces/models'
 import './Login.css'
 
 const styles = {}
 
-interface LoginState {
-  status: number
-  message: string
+interface AccountLoginFormModel {
+  model: AccountLoginModel
+  error?: string
 }
 
-interface LoginFormModel {
-  model: LoginModel
+interface CompetitionLoginFormModel {
+  model: CompetitionLoginModel
   error?: string
 }
 
@@ -25,21 +25,32 @@ interface ServerResponse {
   message: string
 }
 
-const schema: Yup.SchemaOf<LoginFormModel> = Yup.object({
+const accountSchema: Yup.SchemaOf<AccountLoginFormModel> = Yup.object({
   model: Yup.object()
     .shape({
       email: Yup.string().email('Email inte giltig').required('Email krävs'),
       password: Yup.string()
         .required('Lösenord krävs')
-        .min(6, 'Lösenord måste vara minst 6 karaktärer')
+        .min(6, 'Lösenord måste vara minst 6 tecken')
+    })
+    .required(),
+  error: Yup.string().optional()
+})
+
+const competitionSchema: Yup.SchemaOf<CompetitionLoginFormModel> = Yup.object({
+  model: Yup.object()
+    .shape({
+      code: Yup.string()
+        .required('Mata in kod')
+        .min(6, 'Koden måste vara minst 6 karaktärer')
     })
     .required(),
   error: Yup.string().optional()
 })
 
-const handleSubmit = async (
-  values: LoginFormModel,
-  actions: FormikHelpers<LoginFormModel>
+const handleAccountSubmit = async (
+  values: AccountLoginFormModel,
+  actions: FormikHelpers<AccountLoginFormModel>
 ) => {
   await axios
     .post<ServerResponse>(`users/login`, values.model)
@@ -54,15 +65,41 @@ const handleSubmit = async (
     })
 }
 
-const LoginForm: React.FC = (props) => {
-  const [serverState, setServerState] = useState<LoginFormModel>()
-  const initialValues: LoginFormModel = { model: { email: '', password: '' } }
-  return (
-    <div className="login-page">
+const handleCompetitionSubmit = async (
+  values: CompetitionLoginFormModel,
+  actions: FormikHelpers<CompetitionLoginFormModel>
+) => {
+  await axios
+    .post<ServerResponse>(`users/login`, values.model)
+    .then((res) => {
+      actions.resetForm()
+    })
+    .catch(({ response }) => {
+      actions.setFieldError('error', response.data.message)
+    })
+    .finally(() => {
+      actions.setSubmitting(false)
+    })
+}
+
+interface TabPanelProps {
+  activeTab: number
+}
+
+function LoginTab(props: TabPanelProps) {
+  const accountInitialValues: AccountLoginFormModel = {
+    model: { email: '', password: '' }
+  }
+  const competitionInitialValues: CompetitionLoginFormModel = {
+    model: { code: '' }
+  }
+  const { activeTab } = props
+  if (activeTab === 0) {
+    return (
       <Formik
-        initialValues={initialValues}
-        validationSchema={schema}
-        onSubmit={handleSubmit}
+        initialValues={accountInitialValues}
+        validationSchema={accountSchema}
+        onSubmit={handleAccountSubmit}
       >
         {(formik) => (
           <form onSubmit={formik.handleSubmit} className="login-form">
@@ -98,7 +135,7 @@ const LoginForm: React.FC = (props) => {
               color="primary"
               disabled={!formik.isValid}
             >
-              Submit
+              Login
             </Button>
             {formik.errors.error ? (
               <Alert severity="error">
@@ -111,6 +148,73 @@ const LoginForm: React.FC = (props) => {
           </form>
         )}
       </Formik>
+    )
+  }
+  return (
+    <Formik
+      initialValues={competitionInitialValues}
+      validationSchema={competitionSchema}
+      onSubmit={handleCompetitionSubmit}
+    >
+      {(formik) => (
+        <form onSubmit={formik.handleSubmit} className="login-form">
+          <TextField
+            label="Tävlingskod"
+            name="model.code"
+            helperText={
+              formik.touched.model?.code ? formik.errors.model?.code : ''
+            }
+            error={Boolean(formik.errors.model?.code)}
+            onChange={formik.handleChange}
+            onBlur={formik.handleBlur}
+            margin="normal"
+          />
+          <Button
+            type="submit"
+            fullWidth
+            variant="contained"
+            color="primary"
+            disabled={!formik.isValid}
+          >
+            Login
+          </Button>
+          {formik.errors.error ? (
+            <Alert severity="error">
+              <AlertTitle>Error</AlertTitle>
+              {formik.errors.error}
+            </Alert>
+          ) : (
+            <div />
+          )}
+        </form>
+      )}
+    </Formik>
+  )
+}
+
+const useStyles = makeStyles((theme: Theme) => ({
+  root: {
+    backgroundColor: theme.palette.background.paper
+  }
+}))
+
+const LoginForm: React.FC = (props) => {
+  const classes = useStyles()
+  const [loginTab, setLoginTab] = React.useState(0)
+  return (
+    <div className="login-page">
+      <div className={classes.root}>
+        <AppBar position="static">
+          <Tabs
+            value={loginTab}
+            onChange={(event, selectedTab) => setLoginTab(selectedTab)}
+          >
+            <Tab label="Konto" id="simple-tab-0" />
+            <Tab label="Tävling" id="simple-tab-1" />
+          </Tabs>
+        </AppBar>
+        <LoginTab activeTab={loginTab} />
+      </div>
     </div>
   )
 }
diff --git a/client/src/interfaces/models.ts b/client/src/interfaces/models.ts
index 292554f4..f2bc0b12 100644
--- a/client/src/interfaces/models.ts
+++ b/client/src/interfaces/models.ts
@@ -1,4 +1,8 @@
-export interface LoginModel {
+export interface AccountLoginModel {
   email: string
   password: string
 }
+
+export interface CompetitionLoginModel {
+  code: string
+}
-- 
GitLab


From 0bbb3513855ea3342ab6795f5d0642928e2f13b1 Mon Sep 17 00:00:00 2001
From: Victor <viclo211@student.liu.se>
Date: Tue, 2 Mar 2021 15:28:37 +0100
Subject: [PATCH 02/10] Change eslint and prettier settings

---
 client/.eslintrc                    | 78 +++++++++--------------------
 client/.prettierrc                  | 13 ++---
 client/src/App.tsx                  |  5 +-
 client/src/components/AdminView.css |  4 +-
 client/src/components/AdminView.tsx | 29 ++++-------
 client/src/components/Login.tsx     | 35 +++----------
 client/src/index.css                |  8 ++-
 client/tsconfig.json                | 15 ++++--
 8 files changed, 64 insertions(+), 123 deletions(-)

diff --git a/client/.eslintrc b/client/.eslintrc
index 46369ae6..b83ef431 100644
--- a/client/.eslintrc
+++ b/client/.eslintrc
@@ -1,57 +1,27 @@
 {
-    "env": {
-        "browser": true,
-        "es6": true,
-        "node": true
-    },
-    "parser": "@typescript-eslint/parser",
-    "parserOptions": {
-        "ecmaFeatures": {
-            "jsx": true
-        },
-        "project": [
-            "tsconfig.json"
-        ],
-        "ecmaVersion": 2021,
-        "sourceType": "module"
-    },
-    "settings": {
-        "react": {
-            "version": "detect"
-        }
-    },
-    "extends": [
-        "airbnb-typescript",
-        "plugin:@typescript-eslint/recommended",
-        "plugin:react/recommended",
-        "prettier/@typescript-eslint",
-        "plugin:prettier/recommended"
-    ],
-    "rules": {
-        
-        "semi": "off",
-        "react/jsx-one-expression-per-line": "off",
-
-        "prettier/prettier": [
-            "error",
-            {
-                "endOfLine": "auto"
-            }
-        ],
-        "jsx-a11y/label-has-associated-control": [
-            2,
-            {
-                "labelComponents": [
-                    "CustomInputLabel"
-                ],
-                "labelAttributes": [
-                    "label"
-                ],
-                "controlComponents": [
-                    "CustomInput"
-                ],
-                "depth": 3
-            }
-        ]
+  "parser": "@typescript-eslint/parser",
+  "parserOptions": {
+      "ecmaVersion": 2021,
+      "sourceType": "module",
+      "project": [
+        "tsconfig.json"
+      ]
+  },
+  "ecmaFeatures": {
+    "jsx": true
+  },
+  "settings": {
+    "react": {
+      "version": "detect"
     }
+  },
+  "extends": [
+      "plugin:react/recommended",
+      "plugin:@typescript-eslint/recommended",
+      "prettier/@typescript-eslint",
+      "plugin:prettier/recommended"
+  ],
+  "rules": {
+    "prettier/prettier": ["warn"]
+  }
 }
\ No newline at end of file
diff --git a/client/.prettierrc b/client/.prettierrc
index 0cc6d643..4ff5f098 100644
--- a/client/.prettierrc
+++ b/client/.prettierrc
@@ -1,7 +1,8 @@
 {
-    "semi": false,
-    "trailingComma": "none",
-    "singleQuote": true,
-    "printWidth": 80,
-    "endOfLine": "lf"
-  }
\ No newline at end of file
+  "semi": false,
+  "trailingComma": "es5",
+  "singleQuote": true,
+  "printWidth": 120,
+  "tabWidth": 2,
+  "endOfLine": "lf"
+}
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 1e8c051e..105a44d5 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -5,10 +5,7 @@ import Main from './Main'
 const App: React.FC = () => {
   return (
     <div className="wrapper">
-      <link
-        rel="stylesheet"
-        href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
-      />
+      <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
       <Main />
     </div>
   )
diff --git a/client/src/components/AdminView.css b/client/src/components/AdminView.css
index 1ad0b7ee..a310f123 100644
--- a/client/src/components/AdminView.css
+++ b/client/src/components/AdminView.css
@@ -4,7 +4,7 @@
 }
 
 .top-bar {
-  display:flex;
+  display: flex;
   justify-content: space-between;
   align-items: flex-start;
-}
\ No newline at end of file
+}
diff --git a/client/src/components/AdminView.tsx b/client/src/components/AdminView.tsx
index 54551b64..1b4ad513 100644
--- a/client/src/components/AdminView.tsx
+++ b/client/src/components/AdminView.tsx
@@ -9,7 +9,7 @@ import {
   ListItemIcon,
   ListItemText,
   Toolbar,
-  Typography
+  Typography,
 } from '@material-ui/core'
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
 import DashboardIcon from '@material-ui/icons/Dashboard'
@@ -26,27 +26,27 @@ const menuItems = ['Startsida', 'Regioner', 'Användare', 'Tävlingshanterare']
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
     root: {
-      display: 'flex'
+      display: 'flex',
     },
     appBar: {
       width: `calc(100% - ${drawerWidth}px)`,
-      marginLeft: drawerWidth
+      marginLeft: drawerWidth,
     },
     drawer: {
       width: drawerWidth,
       flexShrink: 0,
-      marginRight: drawerWidth
+      marginRight: drawerWidth,
     },
     drawerPaper: {
-      width: drawerWidth
+      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)
-    }
+      paddingLeft: theme.spacing(30),
+    },
   })
 )
 
@@ -70,7 +70,7 @@ const AdminView: React.FC = (props) => {
         className={(classes.drawer, 'background')}
         variant="permanent"
         classes={{
-          paper: classes.drawerPaper
+          paper: classes.drawerPaper,
         }}
         anchor="left"
       >
@@ -87,9 +87,7 @@ const AdminView: React.FC = (props) => {
                 selected={index === openIndex}
                 onClick={() => setOpenIndex(index)}
               >
-                <ListItemIcon>
-                  {text === 'Dashboard' ? <DashboardIcon /> : <MailIcon />}
-                </ListItemIcon>
+                <ListItemIcon>{text === 'Dashboard' ? <DashboardIcon /> : <MailIcon />}</ListItemIcon>
                 <ListItemText primary={text} />
               </ListItem>
             ))}
@@ -97,14 +95,7 @@ const AdminView: React.FC = (props) => {
           <Divider />
           <List>
             <ListItem>
-              <Button
-                component={Link}
-                to="/"
-                type="submit"
-                fullWidth
-                variant="contained"
-                color="primary"
-              >
+              <Button component={Link} to="/" type="submit" fullWidth variant="contained" color="primary">
                 Logga ut
               </Button>
             </ListItem>
diff --git a/client/src/components/Login.tsx b/client/src/components/Login.tsx
index 91af9b78..0b504f24 100644
--- a/client/src/components/Login.tsx
+++ b/client/src/components/Login.tsx
@@ -29,18 +29,13 @@ const schema: Yup.SchemaOf<LoginFormModel> = Yup.object({
   model: Yup.object()
     .shape({
       email: Yup.string().email('Email inte giltig').required('Email krävs'),
-      password: Yup.string()
-        .required('Lösenord krävs')
-        .min(6, 'Lösenord måste vara minst 6 karaktärer')
+      password: Yup.string().required('Lösenord krävs').min(6, 'Lösenord måste vara minst 6 karaktärer'),
     })
     .required(),
-  error: Yup.string().optional()
+  error: Yup.string().optional(),
 })
 
-const handleSubmit = async (
-  values: LoginFormModel,
-  actions: FormikHelpers<LoginFormModel>
-) => {
+const handleSubmit = async (values: LoginFormModel, actions: FormikHelpers<LoginFormModel>) => {
   await axios
     .post<ServerResponse>(`users/login`, values.model)
     .then((res) => {
@@ -59,19 +54,13 @@ const LoginForm: React.FC = (props) => {
   const initialValues: LoginFormModel = { model: { email: '', password: '' } }
   return (
     <div className="login-page">
-      <Formik
-        initialValues={initialValues}
-        validationSchema={schema}
-        onSubmit={handleSubmit}
-      >
+      <Formik initialValues={initialValues} validationSchema={schema} onSubmit={handleSubmit}>
         {(formik) => (
           <form onSubmit={formik.handleSubmit} className="login-form">
             <TextField
               label="Email Adress"
               name="model.email"
-              helperText={
-                formik.touched.model?.email ? formik.errors.model?.email : ''
-              }
+              helperText={formik.touched.model?.email ? formik.errors.model?.email : ''}
               error={Boolean(formik.errors.model?.email)}
               onChange={formik.handleChange}
               onBlur={formik.handleBlur}
@@ -81,23 +70,13 @@ const LoginForm: React.FC = (props) => {
               label="Lösenord"
               name="model.password"
               type="password"
-              helperText={
-                formik.touched.model?.password
-                  ? formik.errors.model?.password
-                  : ''
-              }
+              helperText={formik.touched.model?.password ? formik.errors.model?.password : ''}
               error={Boolean(formik.errors.model?.password)}
               onChange={formik.handleChange}
               onBlur={formik.handleBlur}
               margin="normal"
             />
-            <Button
-              type="submit"
-              fullWidth
-              variant="contained"
-              color="primary"
-              disabled={!formik.isValid}
-            >
+            <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
               Submit
             </Button>
             {formik.errors.error ? (
diff --git a/client/src/index.css b/client/src/index.css
index ec2585e8..7323ae85 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -1,13 +1,11 @@
 body {
   margin: 0;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-    sans-serif;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
+    'Droid Sans', 'Helvetica Neue', sans-serif;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
 }
 
 code {
-  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-    monospace;
+  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
 }
diff --git a/client/tsconfig.json b/client/tsconfig.json
index a273b0cf..7f162ff3 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -1,26 +1,31 @@
 {
   "compilerOptions": {
+    "rootDir": "./src",
+    "outDir": "./build",
+    "esModuleInterop": true,
+    "jsx": "react-jsx",
     "target": "es5",
     "lib": [
       "dom",
       "dom.iterable",
       "esnext"
     ],
-    "allowJs": true,
     "skipLibCheck": true,
-    "esModuleInterop": true,
     "allowSyntheticDefaultImports": true,
     "strict": true,
     "forceConsistentCasingInFileNames": true,
-    "noFallthroughCasesInSwitch": true,
     "module": "esnext",
     "moduleResolution": "node",
     "resolveJsonModule": true,
     "isolatedModules": true,
     "noEmit": true,
-    "jsx": "react-jsx"
+    "allowJs": true,
+    "noFallthroughCasesInSwitch": true
   },
   "include": [
-    "src"
+    "./src/**/*"
+  ],
+  "exclude": [
+    "build"
   ]
 }
-- 
GitLab


From caf343aa89d4a34a0bf10a76f2e5e07c2bcd6fad Mon Sep 17 00:00:00 2001
From: Victor <viclo211@student.liu.se>
Date: Tue, 2 Mar 2021 15:40:55 +0100
Subject: [PATCH 03/10] #21: Remove depracted option and add prettierrc path

---
 .vscode/settings.json | 1 +
 client/.eslintrc      | 1 -
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 896cb947..d98ed346 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -22,6 +22,7 @@
     "eslint.options": {
         "configFile":"./.eslintrc"
     },
+    "prettier.configPath": "./client/.prettierrc",
     //git
     "git.ignoreLimitWarning": true,
     //language specific
diff --git a/client/.eslintrc b/client/.eslintrc
index b83ef431..7e2cadb0 100644
--- a/client/.eslintrc
+++ b/client/.eslintrc
@@ -1,7 +1,6 @@
 {
   "parser": "@typescript-eslint/parser",
   "parserOptions": {
-      "ecmaVersion": 2021,
       "sourceType": "module",
       "project": [
         "tsconfig.json"
-- 
GitLab


From dd5ee6927815a0730a8047338b7c510635feeaf0 Mon Sep 17 00:00:00 2001
From: Victor <viclo211@student.liu.se>
Date: Tue, 2 Mar 2021 20:32:10 +0100
Subject: [PATCH 04/10] #21: Cleanup login code

---
 client/src/components/Login.tsx | 151 +++++++++++++-------------------
 1 file changed, 62 insertions(+), 89 deletions(-)

diff --git a/client/src/components/Login.tsx b/client/src/components/Login.tsx
index 9912b7b1..1cda8296 100644
--- a/client/src/components/Login.tsx
+++ b/client/src/components/Login.tsx
@@ -1,8 +1,7 @@
 import { AppBar, Button, Tab, Tabs, TextField } from '@material-ui/core'
 import { makeStyles, Theme, withStyles } from '@material-ui/core/styles'
-import { Alert, AlertTitle } from '@material-ui/lab'
 import axios from 'axios'
-import { Formik, FormikHelpers } from 'formik'
+import { FieldAttributes, Form, Formik, FormikHelpers, useField } from 'formik'
 import React from 'react'
 import * as Yup from 'yup'
 import { AccountLoginModel, CompetitionLoginModel } from '../interfaces/models'
@@ -25,23 +24,13 @@ interface ServerResponse {
   message: string
 }
 
-const accountSchema: Yup.SchemaOf<AccountLoginFormModel> = Yup.object({
-  model: Yup.object()
-    .shape({
-      email: Yup.string().email('Email inte giltig').required('Email krävs'),
-      password: Yup.string().required('Lösenord krävs').min(6, 'Lösenord måste vara minst 6 karaktärer'),
-    })
-    .required(),
-  error: Yup.string().optional(),
+const accountLoginSchema: Yup.SchemaOf<AccountLoginModel> = Yup.object({
+  email: Yup.string().required('Emailadress krävs').email('Ogiltig emailadress'),
+  password: Yup.string().required('Lösenord krävs').min(8, 'Lösenord måste vara minst 6 tecken'),
 })
 
-const competitionSchema: Yup.SchemaOf<CompetitionLoginFormModel> = Yup.object({
-  model: Yup.object()
-    .shape({
-      code: Yup.string().required('Mata in kod').min(6, 'Koden måste vara minst 6 karaktärer'),
-    })
-    .required(),
-  error: Yup.string().optional(),
+const competitionSchema: Yup.SchemaOf<CompetitionLoginModel> = Yup.object({
+  code: Yup.string().required('Mata in kod').length(4, 'Koden måste vara 4 tecken'),
 })
 
 const handleAccountSubmit = async (values: AccountLoginFormModel, actions: FormikHelpers<AccountLoginFormModel>) => {
@@ -79,86 +68,70 @@ interface TabPanelProps {
   activeTab: number
 }
 
+const CustomInputField = (props: FieldAttributes<any>) => {
+  const [field, meta] = useField<Record<string, unknown>>(props)
+  const errorText = meta.error && meta.touched ? meta.error : ''
+  return <TextField {...field} {...props} helperText={errorText} type="input" margin="normal"></TextField>
+}
+
 function LoginTab(props: TabPanelProps) {
-  const accountInitialValues: AccountLoginFormModel = {
-    model: { email: '', password: '' },
+  const accountInitialValues: AccountLoginModel = {
+    email: '',
+    password: '',
   }
-  const competitionInitialValues: CompetitionLoginFormModel = {
-    model: { code: '' },
+  const competitionInitalValues: CompetitionLoginModel = {
+    code: '',
   }
   const { activeTab } = props
   if (activeTab === 0) {
     return (
-      <Formik initialValues={accountInitialValues} validationSchema={accountSchema} onSubmit={handleAccountSubmit}>
-        {(formik) => (
-          <form onSubmit={formik.handleSubmit} className="login-form">
-            <TextField
-              label="Email Adress"
-              name="model.email"
-              helperText={formik.touched.model?.email ? formik.errors.model?.email : ''}
-              error={Boolean(formik.errors.model?.email)}
-              onChange={formik.handleChange}
-              onBlur={formik.handleBlur}
-              margin="normal"
-            />
-            <TextField
-              label="Lösenord"
-              name="model.password"
-              type="password"
-              helperText={formik.touched.model?.password ? formik.errors.model?.password : ''}
-              error={Boolean(formik.errors.model?.password)}
-              onChange={formik.handleChange}
-              onBlur={formik.handleBlur}
-              margin="normal"
-            />
-            <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
-              Logga in
-            </Button>
-            {formik.errors.error ? (
-              <Alert severity="error">
-                <AlertTitle>Error</AlertTitle>
-                {formik.errors.error}
-              </Alert>
-            ) : (
-              <div />
-            )}
-          </form>
-        )}
-      </Formik>
+      <div>
+        <Formik
+          initialValues={accountInitialValues}
+          validationSchema={accountLoginSchema}
+          onSubmit={(values: AccountLoginModel) => {
+            console.log('Login')
+          }}
+        >
+          {(formik) => (
+            <Form className="login-form">
+              <CustomInputField name="email" label="Emailadress" error={'email' in formik.errors}></CustomInputField>
+              <CustomInputField name="password" label="Lösenord" error={'password' in formik.errors}></CustomInputField>
+              <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
+                Logga in
+              </Button>
+              {/* <pre>{JSON.stringify(formik.values, null, 2)}</pre>
+              <pre>{JSON.stringify(formik.errors, null, 2)}</pre> */}
+            </Form>
+          )}
+        </Formik>
+      </div>
     )
-  }
-  return (
-    <Formik
-      initialValues={competitionInitialValues}
-      validationSchema={competitionSchema}
-      onSubmit={handleCompetitionSubmit}
-    >
-      {(formik) => (
-        <form onSubmit={formik.handleSubmit} className="login-form">
-          <TextField
-            label="Tävlingskod"
-            name="model.code"
-            helperText={formik.touched.model?.code ? formik.errors.model?.code : ''}
-            error={Boolean(formik.errors.model?.code)}
-            onChange={formik.handleChange}
-            onBlur={formik.handleBlur}
-            margin="normal"
-          />
-          <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
-            Login
-          </Button>
-          {formik.errors.error ? (
-            <Alert severity="error">
-              <AlertTitle>Error</AlertTitle>
-              {formik.errors.error}
-            </Alert>
-          ) : (
-            <div />
+  } else if (activeTab === 1) {
+    return (
+      <div>
+        <Formik
+          initialValues={competitionInitalValues}
+          validationSchema={competitionSchema}
+          onSubmit={(values: CompetitionLoginModel) => {
+            console.log('Connect')
+          }}
+        >
+          {(formik) => (
+            <Form className="login-form">
+              <CustomInputField name="code" label="Tävlingskod" error={'code' in formik.errors}></CustomInputField>
+              <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
+                Anslut till tävling
+              </Button>
+              {/* <pre>values: {JSON.stringify(formik.values, null, 2)}</pre>
+              <pre>errors: {JSON.stringify(formik.errors, null, 2)}</pre> */}
+            </Form>
           )}
-        </form>
-      )}
-    </Formik>
-  )
+        </Formik>
+      </div>
+    )
+  }
+  return <div></div>
 }
 
 const useStyles = makeStyles((theme: Theme) => ({
-- 
GitLab


From ca1285b679942e38060c0215931984e7b4594c3e Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Wed, 3 Mar 2021 12:08:13 +0100
Subject: [PATCH 05/10] #21: Added view selection and pages for individual
 views

---
 client/src/Main.tsx                           |  12 +-
 client/src/components/AdminView.tsx           |   4 +-
 client/src/components/Login.tsx               | 161 ---------------
 .../Login.css => pages/LoginPage.css}         |   0
 .../LoginPage.test.tsx}                       |   4 +-
 client/src/pages/LoginPage.tsx                | 192 ++++++++++++++++++
 client/src/pages/views/AudienceViewPage.tsx   |   7 +
 client/src/pages/views/JudgeViewPage.tsx      |   8 +
 .../src/pages/views/ParticipantViewPage.tsx   |   7 +
 client/src/pages/views/ViewSelectPage.css     |  21 ++
 client/src/pages/views/ViewSelectPage.tsx     |  23 +++
 11 files changed, 271 insertions(+), 168 deletions(-)
 delete mode 100644 client/src/components/Login.tsx
 rename client/src/{components/Login.css => pages/LoginPage.css} (100%)
 rename client/src/{components/Login.test.tsx => pages/LoginPage.test.tsx} (64%)
 create mode 100644 client/src/pages/LoginPage.tsx
 create mode 100644 client/src/pages/views/AudienceViewPage.tsx
 create mode 100644 client/src/pages/views/JudgeViewPage.tsx
 create mode 100644 client/src/pages/views/ParticipantViewPage.tsx
 create mode 100644 client/src/pages/views/ViewSelectPage.css
 create mode 100644 client/src/pages/views/ViewSelectPage.tsx

diff --git a/client/src/Main.tsx b/client/src/Main.tsx
index a44422fd..33bfc5f2 100644
--- a/client/src/Main.tsx
+++ b/client/src/Main.tsx
@@ -1,14 +1,22 @@
 import React from 'react'
 import { BrowserRouter, Route, Switch } from 'react-router-dom'
 import AdminView from './components/AdminView'
-import LoginForm from './components/Login'
+import LoginPage from './pages/LoginPage'
+import AudienceViewPage from './pages/views/AudienceViewPage'
+import JudgeViewPage from './pages/views/JudgeViewPage'
+import ParticipantViewPage from './pages/views/ParticipantViewPage'
+import ViewSelectPage from './pages/views/ViewSelectPage'
 
 const Main = () => {
   return (
     <BrowserRouter>
       <Switch>
-        <Route exact path="/" component={LoginForm} />
+        <Route exact path="/" component={LoginPage} />
         <Route path="/admin" component={AdminView} />
+        <Route exact path="/view" component={ViewSelectPage} />
+        <Route exact path="/view/participant" component={ParticipantViewPage} />
+        <Route exact path="/view/judge" component={JudgeViewPage} />
+        <Route exact path="/view/audience" component={AudienceViewPage} />
       </Switch>
     </BrowserRouter>
   )
diff --git a/client/src/components/AdminView.tsx b/client/src/components/AdminView.tsx
index 1b4ad513..59f8f67f 100644
--- a/client/src/components/AdminView.tsx
+++ b/client/src/components/AdminView.tsx
@@ -53,9 +53,7 @@ const useStyles = makeStyles((theme: Theme) =>
 const AdminView: React.FC = (props) => {
   const classes = useStyles()
   const [openIndex, setOpenIndex] = React.useState(0)
-  const match = useRouteMatch()
-  console.log(match)
-  const { path, url } = match
+  const { path, url } = useRouteMatch()
   return (
     <div className={classes.root}>
       <CssBaseline />
diff --git a/client/src/components/Login.tsx b/client/src/components/Login.tsx
deleted file mode 100644
index 1cda8296..00000000
--- a/client/src/components/Login.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import { AppBar, Button, Tab, Tabs, TextField } from '@material-ui/core'
-import { makeStyles, Theme, withStyles } from '@material-ui/core/styles'
-import axios from 'axios'
-import { FieldAttributes, Form, Formik, FormikHelpers, useField } from 'formik'
-import React from 'react'
-import * as Yup from 'yup'
-import { AccountLoginModel, CompetitionLoginModel } from '../interfaces/models'
-import './Login.css'
-
-const styles = {}
-
-interface AccountLoginFormModel {
-  model: AccountLoginModel
-  error?: string
-}
-
-interface CompetitionLoginFormModel {
-  model: CompetitionLoginModel
-  error?: string
-}
-
-interface ServerResponse {
-  code: number
-  message: string
-}
-
-const accountLoginSchema: Yup.SchemaOf<AccountLoginModel> = Yup.object({
-  email: Yup.string().required('Emailadress krävs').email('Ogiltig emailadress'),
-  password: Yup.string().required('Lösenord krävs').min(8, 'Lösenord måste vara minst 6 tecken'),
-})
-
-const competitionSchema: Yup.SchemaOf<CompetitionLoginModel> = Yup.object({
-  code: Yup.string().required('Mata in kod').length(4, 'Koden måste vara 4 tecken'),
-})
-
-const handleAccountSubmit = async (values: AccountLoginFormModel, actions: FormikHelpers<AccountLoginFormModel>) => {
-  await axios
-    .post<ServerResponse>(`users/login`, values.model)
-    .then((res) => {
-      actions.resetForm()
-    })
-    .catch(({ response }) => {
-      actions.setFieldError('error', response.data.message)
-    })
-    .finally(() => {
-      actions.setSubmitting(false)
-    })
-}
-
-const handleCompetitionSubmit = async (
-  values: CompetitionLoginFormModel,
-  actions: FormikHelpers<CompetitionLoginFormModel>
-) => {
-  await axios
-    .post<ServerResponse>(`users/login`, values.model)
-    .then((res) => {
-      actions.resetForm()
-    })
-    .catch(({ response }) => {
-      actions.setFieldError('error', response.data.message)
-    })
-    .finally(() => {
-      actions.setSubmitting(false)
-    })
-}
-
-interface TabPanelProps {
-  activeTab: number
-}
-
-const CustomInputField = (props: FieldAttributes<any>) => {
-  const [field, meta] = useField<Record<string, unknown>>(props)
-  const errorText = meta.error && meta.touched ? meta.error : ''
-  return <TextField {...field} {...props} helperText={errorText} type="input" margin="normal"></TextField>
-}
-
-function LoginTab(props: TabPanelProps) {
-  const accountInitialValues: AccountLoginModel = {
-    email: '',
-    password: '',
-  }
-  const competitionInitalValues: CompetitionLoginModel = {
-    code: '',
-  }
-  const { activeTab } = props
-  if (activeTab === 0) {
-    return (
-      <div>
-        <Formik
-          initialValues={accountInitialValues}
-          validationSchema={accountLoginSchema}
-          onSubmit={(values: AccountLoginModel) => {
-            console.log('Login')
-          }}
-        >
-          {(formik) => (
-            <Form className="login-form">
-              <CustomInputField name="email" label="Emailadress" error={'email' in formik.errors}></CustomInputField>
-              <CustomInputField name="password" label="Lösenord" error={'password' in formik.errors}></CustomInputField>
-              <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
-                Logga in
-              </Button>
-              {/* <pre>{JSON.stringify(formik.values, null, 2)}</pre>
-              <pre>{JSON.stringify(formik.errors, null, 2)}</pre> */}
-            </Form>
-          )}
-        </Formik>
-      </div>
-    )
-  } else if (activeTab === 1) {
-    return (
-      <div>
-        <Formik
-          initialValues={competitionInitalValues}
-          validationSchema={competitionSchema}
-          onSubmit={(values: CompetitionLoginModel) => {
-            console.log('Connect')
-          }}
-        >
-          {(formik) => (
-            <Form className="login-form">
-              <CustomInputField name="code" label="Tävlingskod" error={'code' in formik.errors}></CustomInputField>
-              <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
-                Anslut till tävling
-              </Button>
-              {/* <pre>values: {JSON.stringify(formik.values, null, 2)}</pre>
-              <pre>errors: {JSON.stringify(formik.errors, null, 2)}</pre> */}
-            </Form>
-          )}
-        </Formik>
-      </div>
-    )
-  }
-  return <div></div>
-}
-
-const useStyles = makeStyles((theme: Theme) => ({
-  root: {
-    backgroundColor: theme.palette.background.paper,
-  },
-}))
-
-const LoginForm: React.FC = (props) => {
-  const classes = useStyles()
-  const [loginTab, setLoginTab] = React.useState(0)
-  return (
-    <div className="login-page">
-      <div className={classes.root}>
-        <AppBar position="static">
-          <Tabs value={loginTab} onChange={(event, selectedTab) => setLoginTab(selectedTab)}>
-            <Tab label="Konto" id="simple-tab-0" />
-            <Tab label="Tävling" id="simple-tab-1" />
-          </Tabs>
-        </AppBar>
-        <LoginTab activeTab={loginTab} />
-      </div>
-    </div>
-  )
-}
-
-export default withStyles(styles)(LoginForm)
diff --git a/client/src/components/Login.css b/client/src/pages/LoginPage.css
similarity index 100%
rename from client/src/components/Login.css
rename to client/src/pages/LoginPage.css
diff --git a/client/src/components/Login.test.tsx b/client/src/pages/LoginPage.test.tsx
similarity index 64%
rename from client/src/components/Login.test.tsx
rename to client/src/pages/LoginPage.test.tsx
index 12a6c79c..ae41ba02 100644
--- a/client/src/components/Login.test.tsx
+++ b/client/src/pages/LoginPage.test.tsx
@@ -1,7 +1,7 @@
 import { render } from '@testing-library/react'
 import React from 'react'
-import LoginForm from './Login'
+import LoginPage from './LoginPage'
 
 it('renders login form', () => {
-  render(<LoginForm />)
+  render(<LoginPage />)
 })
diff --git a/client/src/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx
new file mode 100644
index 00000000..ec3757e4
--- /dev/null
+++ b/client/src/pages/LoginPage.tsx
@@ -0,0 +1,192 @@
+import { AppBar, Button, Tab, Tabs, TextField } from '@material-ui/core'
+import { makeStyles, Theme, withStyles } from '@material-ui/core/styles'
+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 { AccountLoginModel, CompetitionLoginModel } from '../interfaces/models'
+import './LoginPage.css'
+
+const styles = {}
+
+interface AccountLoginFormModel {
+  model: AccountLoginModel
+  error?: string
+}
+
+interface CompetitionLoginFormModel {
+  model: CompetitionLoginModel
+  error?: string
+}
+
+interface ServerResponse {
+  code: number
+  message: string
+}
+
+const accountSchema: Yup.SchemaOf<AccountLoginFormModel> = Yup.object({
+  model: Yup.object()
+    .shape({
+      email: Yup.string().email('Email inte giltig').required('Email krävs'),
+      password: Yup.string().required('Lösenord krävs').min(6, 'Lösenord måste vara minst 6 tecken'),
+    })
+    .required(),
+  error: Yup.string().optional(),
+})
+
+const competitionSchema: Yup.SchemaOf<CompetitionLoginFormModel> = Yup.object({
+  model: Yup.object()
+    .shape({
+      code: Yup.string().required('Mata in kod').min(6, 'Koden måste vara minst 6 karaktärer'),
+    })
+    .required(),
+  error: Yup.string().optional(),
+})
+
+const handleAccountSubmit = async (values: AccountLoginFormModel, actions: FormikHelpers<AccountLoginFormModel>) => {
+  await axios
+    .post<ServerResponse>(`users/login`, values.model)
+    .then((res) => {
+      actions.resetForm()
+    })
+    .catch(({ response }) => {
+      actions.setFieldError('error', response.data.message)
+    })
+    .finally(() => {
+      actions.setSubmitting(false)
+    })
+}
+
+const handleCompetitionSubmit = async (
+  values: CompetitionLoginFormModel,
+  actions: FormikHelpers<CompetitionLoginFormModel>
+) => {
+  await axios
+    .post<ServerResponse>(`users/login`, values.model)
+    .then((res) => {
+      actions.resetForm()
+    })
+    .catch(({ response }) => {
+      actions.setFieldError('error', response.data.message)
+    })
+    .finally(() => {
+      actions.setSubmitting(false)
+    })
+}
+
+interface TabPanelProps {
+  activeTab: number
+}
+
+function LoginTab(props: TabPanelProps) {
+  const accountInitialValues: AccountLoginFormModel = {
+    model: { email: '', password: '' },
+  }
+  const competitionInitialValues: CompetitionLoginFormModel = {
+    model: { code: '' },
+  }
+  const { activeTab } = props
+  if (activeTab === 0) {
+    return (
+      <Formik initialValues={accountInitialValues} validationSchema={accountSchema} onSubmit={handleAccountSubmit}>
+        {(formik) => (
+          <form onSubmit={formik.handleSubmit} className="login-form">
+            <TextField
+              label="Email Adress"
+              name="model.email"
+              helperText={formik.touched.model?.email ? formik.errors.model?.email : ''}
+              error={Boolean(formik.errors.model?.email)}
+              onChange={formik.handleChange}
+              onBlur={formik.handleBlur}
+              margin="normal"
+            />
+            <TextField
+              label="Lösenord"
+              name="model.password"
+              type="password"
+              helperText={formik.touched.model?.password ? formik.errors.model?.password : ''}
+              error={Boolean(formik.errors.model?.password)}
+              onChange={formik.handleChange}
+              onBlur={formik.handleBlur}
+              margin="normal"
+            />
+            <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
+              Logga in
+            </Button>
+            {formik.errors.error ? (
+              <Alert severity="error">
+                <AlertTitle>Error</AlertTitle>
+                {formik.errors.error}
+              </Alert>
+            ) : (
+              <div />
+            )}
+          </form>
+        )}
+      </Formik>
+    )
+  }
+  return (
+    <Formik
+      initialValues={competitionInitialValues}
+      validationSchema={competitionSchema}
+      onSubmit={handleCompetitionSubmit}
+    >
+      {(formik) => (
+        <form onSubmit={formik.handleSubmit} className="login-form">
+          <TextField
+            label="Tävlingskod"
+            name="model.code"
+            helperText={formik.touched.model?.code ? formik.errors.model?.code : ''}
+            error={Boolean(formik.errors.model?.code)}
+            onChange={formik.handleChange}
+            onBlur={formik.handleBlur}
+            margin="normal"
+          />
+          <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
+            Anslut till tävling
+          </Button>
+          {formik.errors.error ? (
+            <Alert severity="error">
+              <AlertTitle>Error</AlertTitle>
+              {formik.errors.error}
+            </Alert>
+          ) : (
+            <div />
+          )}
+        </form>
+      )}
+    </Formik>
+  )
+}
+
+const useStyles = makeStyles((theme: Theme) => ({
+  root: {
+    backgroundColor: theme.palette.background.paper,
+  },
+}))
+
+const LoginPage: React.FC = (props) => {
+  const classes = useStyles()
+  const [loginTab, setLoginTab] = React.useState(0)
+  return (
+    <div className="login-page">
+      <div className={classes.root}>
+        <AppBar position="static">
+          <Tabs value={loginTab} onChange={(event, selectedTab) => setLoginTab(selectedTab)}>
+            <Tab label="Konto" id="simple-tab-0" />
+            <Tab label="Tävling" id="simple-tab-1" />
+          </Tabs>
+        </AppBar>
+        <LoginTab activeTab={loginTab} />
+      </div>
+    </div>
+  )
+}
+
+export default withStyles(styles)(LoginPage)
+
+// TODO: Values carry over from email address to code and vice versa
+// TODO: Errors in email address causes error messages in password field
+// TODO: Login button is enabled by default but should be turned off as now values have been entered yet
diff --git a/client/src/pages/views/AudienceViewPage.tsx b/client/src/pages/views/AudienceViewPage.tsx
new file mode 100644
index 00000000..f0cd70bc
--- /dev/null
+++ b/client/src/pages/views/AudienceViewPage.tsx
@@ -0,0 +1,7 @@
+import React from 'react'
+
+const AudienceViewPage: React.FC = (props) => {
+  return <div>Publik</div>
+}
+
+export default AudienceViewPage
diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx
new file mode 100644
index 00000000..5498ff8c
--- /dev/null
+++ b/client/src/pages/views/JudgeViewPage.tsx
@@ -0,0 +1,8 @@
+import React from 'react'
+import './ViewSelectPage.css'
+
+const JudgeViewPage: React.FC = (props) => {
+  return <div>Judge</div>
+}
+
+export default JudgeViewPage
diff --git a/client/src/pages/views/ParticipantViewPage.tsx b/client/src/pages/views/ParticipantViewPage.tsx
new file mode 100644
index 00000000..6a22cd44
--- /dev/null
+++ b/client/src/pages/views/ParticipantViewPage.tsx
@@ -0,0 +1,7 @@
+import React from 'react'
+
+const ParticipantViewPage: React.FC = (props) => {
+  return <div>Deltagare</div>
+}
+
+export default ParticipantViewPage
diff --git a/client/src/pages/views/ViewSelectPage.css b/client/src/pages/views/ViewSelectPage.css
new file mode 100644
index 00000000..3a684a19
--- /dev/null
+++ b/client/src/pages/views/ViewSelectPage.css
@@ -0,0 +1,21 @@
+html,
+body {
+    display: flex;
+    justify-content: center;
+    margin-top: 15%;
+    height: 100%;
+}
+
+.root {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    width: max-content;
+    height: 140px;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+.view-button {
+    width: 100%;
+}
diff --git a/client/src/pages/views/ViewSelectPage.tsx b/client/src/pages/views/ViewSelectPage.tsx
new file mode 100644
index 00000000..249dd87c
--- /dev/null
+++ b/client/src/pages/views/ViewSelectPage.tsx
@@ -0,0 +1,23 @@
+import Button from '@material-ui/core/Button'
+import React from 'react'
+import { Link, useRouteMatch } from 'react-router-dom'
+import './ViewSelectPage.css'
+
+const ViewSelectPage: React.FC = (props) => {
+  const { path, url } = useRouteMatch()
+  return (
+    <div className="root">
+      <Button className="view-button" color="primary" variant="contained" component={Link} to={`${url}/participant`}>
+        Deltagarvy
+      </Button>
+      <Button className="view-button" color="primary" variant="contained" component={Link} to={`${url}/audience`}>
+        Åskådarvy
+      </Button>
+      <Button className="view-button" color="primary" variant="contained" component={Link} to={`${url}/judge`}>
+        Domarvy
+      </Button>
+    </div>
+  )
+}
+
+export default ViewSelectPage
-- 
GitLab


From 2aed5820b139b3ec8d0ce462586fcb36ea6a9fc4 Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Wed, 3 Mar 2021 17:05:16 +0100
Subject: [PATCH 06/10] Add competition list

---
 client/src/Main.tsx                           |   4 +-
 client/src/components/CompetitionManager.tsx  |  12 --
 client/src/pages/LoginPage.tsx                |  23 +-
 .../admin/AdminPage.css}                      |   0
 .../admin/AdminPage.test.tsx}                 |   4 +-
 .../admin/AdminPage.tsx}                      |   8 +-
 .../admin/components/CompetitionManager.css   |   9 +
 .../components/CompetitionManager.test.tsx    |   0
 .../admin/components/CompetitionManager.tsx   | 197 ++++++++++++++++++
 .../admin}/components/Regions.test.tsx        |   0
 .../{ => pages/admin}/components/Regions.tsx  |   0
 client/src/pages/views/JudgeViewPage.tsx      |   1 +
 client/src/pages/views/ViewSelectPage.css     |   7 +-
 client/src/pages/views/ViewSelectPage.tsx     |  20 +-
 14 files changed, 243 insertions(+), 42 deletions(-)
 delete mode 100644 client/src/components/CompetitionManager.tsx
 rename client/src/{components/AdminView.css => pages/admin/AdminPage.css} (100%)
 rename client/src/{components/AdminView.test.tsx => pages/admin/AdminPage.test.tsx} (79%)
 rename client/src/{components/AdminView.tsx => pages/admin/AdminPage.tsx} (93%)
 create mode 100644 client/src/pages/admin/components/CompetitionManager.css
 rename client/src/{ => pages/admin}/components/CompetitionManager.test.tsx (100%)
 create mode 100644 client/src/pages/admin/components/CompetitionManager.tsx
 rename client/src/{ => pages/admin}/components/Regions.test.tsx (100%)
 rename client/src/{ => pages/admin}/components/Regions.tsx (100%)

diff --git a/client/src/Main.tsx b/client/src/Main.tsx
index 33bfc5f2..0bae997f 100644
--- a/client/src/Main.tsx
+++ b/client/src/Main.tsx
@@ -1,6 +1,6 @@
 import React from 'react'
 import { BrowserRouter, Route, Switch } from 'react-router-dom'
-import AdminView from './components/AdminView'
+import AdminPage from './pages/admin/AdminPage'
 import LoginPage from './pages/LoginPage'
 import AudienceViewPage from './pages/views/AudienceViewPage'
 import JudgeViewPage from './pages/views/JudgeViewPage'
@@ -12,7 +12,7 @@ const Main = () => {
     <BrowserRouter>
       <Switch>
         <Route exact path="/" component={LoginPage} />
-        <Route path="/admin" component={AdminView} />
+        <Route path="/admin" component={AdminPage} />
         <Route exact path="/view" component={ViewSelectPage} />
         <Route exact path="/view/participant" component={ParticipantViewPage} />
         <Route exact path="/view/judge" component={JudgeViewPage} />
diff --git a/client/src/components/CompetitionManager.tsx b/client/src/components/CompetitionManager.tsx
deleted file mode 100644
index 2d140fa3..00000000
--- a/client/src/components/CompetitionManager.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-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/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx
index ec3757e4..d2fc3848 100644
--- a/client/src/pages/LoginPage.tsx
+++ b/client/src/pages/LoginPage.tsx
@@ -7,7 +7,6 @@ import React from 'react'
 import * as Yup from 'yup'
 import { AccountLoginModel, CompetitionLoginModel } from '../interfaces/models'
 import './LoginPage.css'
-
 const styles = {}
 
 interface AccountLoginFormModel {
@@ -38,7 +37,7 @@ const accountSchema: Yup.SchemaOf<AccountLoginFormModel> = Yup.object({
 const competitionSchema: Yup.SchemaOf<CompetitionLoginFormModel> = Yup.object({
   model: Yup.object()
     .shape({
-      code: Yup.string().required('Mata in kod').min(6, 'Koden måste vara minst 6 karaktärer'),
+      code: Yup.string().required('Mata in kod').min(6, 'Koden måste vara minst 6 tecken'),
     })
     .required(),
   error: Yup.string().optional(),
@@ -62,8 +61,9 @@ const handleCompetitionSubmit = async (
   values: CompetitionLoginFormModel,
   actions: FormikHelpers<CompetitionLoginFormModel>
 ) => {
+  console.log(values.model)
   await axios
-    .post<ServerResponse>(`users/login`, values.model)
+    .post<ServerResponse>(`users/login`, { code: values.model.code })
     .then((res) => {
       actions.resetForm()
     })
@@ -96,7 +96,7 @@ function LoginTab(props: TabPanelProps) {
               label="Email Adress"
               name="model.email"
               helperText={formik.touched.model?.email ? formik.errors.model?.email : ''}
-              error={Boolean(formik.errors.model?.email)}
+              error={Boolean(formik.touched.model?.email && formik.errors.model?.email)}
               onChange={formik.handleChange}
               onBlur={formik.handleBlur}
               margin="normal"
@@ -106,12 +106,18 @@ function LoginTab(props: TabPanelProps) {
               name="model.password"
               type="password"
               helperText={formik.touched.model?.password ? formik.errors.model?.password : ''}
-              error={Boolean(formik.errors.model?.password)}
+              error={Boolean(formik.touched.model?.password && formik.errors.model?.password)}
               onChange={formik.handleChange}
               onBlur={formik.handleBlur}
               margin="normal"
             />
-            <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
+            <Button
+              type="submit"
+              fullWidth
+              variant="contained"
+              color="primary"
+              disabled={!formik.isValid || !formik.touched.model?.email || !formik.touched.model?.email}
+            >
               Logga in
             </Button>
             {formik.errors.error ? (
@@ -138,8 +144,8 @@ function LoginTab(props: TabPanelProps) {
           <TextField
             label="Tävlingskod"
             name="model.code"
-            helperText={formik.touched.model?.code ? formik.errors.model?.code : ''}
-            error={Boolean(formik.errors.model?.code)}
+            helperText={formik.touched.model?.code && formik.touched.model?.code ? formik.errors.model?.code : ''}
+            error={Boolean(formik.touched.model?.code && formik.errors.model?.code)}
             onChange={formik.handleChange}
             onBlur={formik.handleBlur}
             margin="normal"
@@ -189,4 +195,3 @@ export default withStyles(styles)(LoginPage)
 
 // TODO: Values carry over from email address to code and vice versa
 // TODO: Errors in email address causes error messages in password field
-// TODO: Login button is enabled by default but should be turned off as now values have been entered yet
diff --git a/client/src/components/AdminView.css b/client/src/pages/admin/AdminPage.css
similarity index 100%
rename from client/src/components/AdminView.css
rename to client/src/pages/admin/AdminPage.css
diff --git a/client/src/components/AdminView.test.tsx b/client/src/pages/admin/AdminPage.test.tsx
similarity index 79%
rename from client/src/components/AdminView.test.tsx
rename to client/src/pages/admin/AdminPage.test.tsx
index cdc46049..b29b78f8 100644
--- a/client/src/components/AdminView.test.tsx
+++ b/client/src/pages/admin/AdminPage.test.tsx
@@ -1,12 +1,12 @@
 import { render } from '@testing-library/react'
 import React from 'react'
 import { BrowserRouter } from 'react-router-dom'
-import AdminView from './AdminView'
+import AdminPage from './AdminPage'
 
 it('renders admin view', () => {
   render(
     <BrowserRouter>
-      <AdminView />
+      <AdminPage />
     </BrowserRouter>
   )
 })
diff --git a/client/src/components/AdminView.tsx b/client/src/pages/admin/AdminPage.tsx
similarity index 93%
rename from client/src/components/AdminView.tsx
rename to client/src/pages/admin/AdminPage.tsx
index 59f8f67f..a95bf04e 100644
--- a/client/src/components/AdminView.tsx
+++ b/client/src/pages/admin/AdminPage.tsx
@@ -16,9 +16,9 @@ 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'
+import './AdminPage.css'
+import CompetitionManager from './components/CompetitionManager'
+import Regions from './components/Regions'
 
 const drawerWidth = 240
 const menuItems = ['Startsida', 'Regioner', 'Användare', 'Tävlingshanterare']
@@ -85,7 +85,7 @@ const AdminView: React.FC = (props) => {
                 selected={index === openIndex}
                 onClick={() => setOpenIndex(index)}
               >
-                <ListItemIcon>{text === 'Dashboard' ? <DashboardIcon /> : <MailIcon />}</ListItemIcon>
+                <ListItemIcon>{index === 0 ? <DashboardIcon /> : <MailIcon />}</ListItemIcon>
                 <ListItemText primary={text} />
               </ListItem>
             ))}
diff --git a/client/src/pages/admin/components/CompetitionManager.css b/client/src/pages/admin/components/CompetitionManager.css
new file mode 100644
index 00000000..51817660
--- /dev/null
+++ b/client/src/pages/admin/components/CompetitionManager.css
@@ -0,0 +1,9 @@
+.top-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.new-competition-button {
+  margin-bottom: 15px;
+}
diff --git a/client/src/components/CompetitionManager.test.tsx b/client/src/pages/admin/components/CompetitionManager.test.tsx
similarity index 100%
rename from client/src/components/CompetitionManager.test.tsx
rename to client/src/pages/admin/components/CompetitionManager.test.tsx
diff --git a/client/src/pages/admin/components/CompetitionManager.tsx b/client/src/pages/admin/components/CompetitionManager.tsx
new file mode 100644
index 00000000..160b32e4
--- /dev/null
+++ b/client/src/pages/admin/components/CompetitionManager.tsx
@@ -0,0 +1,197 @@
+import { Button, Menu } from '@material-ui/core'
+import FormControl from '@material-ui/core/FormControl'
+import InputBase from '@material-ui/core/InputBase'
+import InputLabel from '@material-ui/core/InputLabel'
+import MenuItem from '@material-ui/core/MenuItem'
+import Paper from '@material-ui/core/Paper'
+import Select from '@material-ui/core/Select'
+import { createStyles, makeStyles, Theme, withStyles } from '@material-ui/core/styles'
+import Table from '@material-ui/core/Table'
+import TableBody from '@material-ui/core/TableBody'
+import TableCell from '@material-ui/core/TableCell'
+import TableContainer from '@material-ui/core/TableContainer'
+import TableHead from '@material-ui/core/TableHead'
+import TableRow from '@material-ui/core/TableRow'
+import MoreHorizIcon from '@material-ui/icons/MoreHoriz'
+import React from 'react'
+import { Link } from 'react-router-dom'
+import './CompetitionManager.css'
+
+const BootstrapInput = withStyles((theme: Theme) =>
+  createStyles({
+    root: {
+      'label + &': {
+        marginTop: theme.spacing(3),
+      },
+    },
+    input: {
+      borderRadius: 4,
+      position: 'relative',
+      backgroundColor: theme.palette.background.paper,
+      border: '1px solid #ced4da',
+      fontSize: 16,
+      padding: '10px 26px 10px 12px',
+      transition: theme.transitions.create(['border-color', 'box-shadow']),
+      '&:focus': {
+        borderRadius: 4,
+        borderColor: '#80bdff',
+        boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
+      },
+    },
+  })
+)(InputBase)
+
+function createCompetition(name: string, region: string, year: number) {
+  return { name, region, year }
+}
+
+const competitions = [
+  createCompetition('Tävling 1', 'Stockholm', 2021),
+  createCompetition('Tävling 2', 'Stockholm', 2020),
+  createCompetition('Tävling 3', 'Sala', 2020),
+  createCompetition('Tävling 4', 'Sundsvall', 2020),
+  createCompetition('Tävling 5', 'Linköping', 2020),
+  createCompetition('Tävling 6', 'Linköping', 2020),
+  createCompetition('Tävling 7', 'Sala', 2019),
+  createCompetition('Tävling 8', 'Stockholm', 2019),
+  createCompetition('Tävling 9', 'Stockholm', 2019),
+  createCompetition('Tävling 10', 'Lidköping', 2019),
+  createCompetition('Tävling 11', 'Stockholm', 2019),
+  createCompetition('Tävling 12', 'Sala', 2018),
+  createCompetition('Tävling 13', 'Tornby', 2018),
+]
+
+const regions = competitions
+  .map((competition) => competition.region)
+  .filter((competition, index, self) => self.indexOf(competition) === index)
+
+const years = competitions
+  .map((competition) => competition.year)
+  .filter((competition, index, self) => self.indexOf(competition) === index)
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    table: {
+      width: 1500, // TODO: Shrink table when smaller screen
+    },
+    margin: {
+      margin: theme.spacing(1),
+    },
+  })
+)
+
+const CompetitionManager: React.FC = (props) => {
+  const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
+  const classes = useStyles()
+  const yearInitialValue = 0
+  const regionInitialValue = ''
+  const noFilterText = 'Alla'
+  const [year, setYear] = React.useState(yearInitialValue)
+  const [region, setRegion] = React.useState(regionInitialValue)
+  const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+    setAnchorEl(event.currentTarget)
+  }
+
+  const handleClose = () => {
+    setAnchorEl(null)
+  }
+  return (
+    <div>
+      <div className="top-bar">
+        <div>
+          <FormControl className={classes.margin}>
+            <InputLabel shrink id="demo-customized-textbox">
+              Sök
+            </InputLabel>
+            <BootstrapInput id="demo-customized-textbox" />
+          </FormControl>
+          <FormControl className={classes.margin}>
+            <InputLabel shrink id="demo-customized-select-native">
+              Region
+            </InputLabel>
+            <Select
+              labelId="demo-customized-select-label"
+              id="demo-customized-select"
+              value={region === regionInitialValue ? noFilterText : region}
+              input={<BootstrapInput />}
+            >
+              <MenuItem value={noFilterText} onClick={() => setRegion(regionInitialValue)}>
+                {noFilterText}
+              </MenuItem>
+              {regions.map((text, index) => (
+                <MenuItem key={text} value={text} onClick={() => setRegion(text)}>
+                  {text}
+                </MenuItem>
+              ))}
+            </Select>
+          </FormControl>
+          <FormControl className={classes.margin}>
+            <InputLabel shrink id="demo-customized-select-label">
+              Ã…r
+            </InputLabel>
+            <Select
+              id="demo-customized-select"
+              value={year === yearInitialValue ? noFilterText : year}
+              input={<BootstrapInput />}
+            >
+              <MenuItem value={noFilterText} onClick={() => setYear(yearInitialValue)}>
+                {noFilterText}
+              </MenuItem>
+              {years.map((year, index) => (
+                <MenuItem key={year} value={year} onClick={() => setYear(year)}>
+                  {year}
+                </MenuItem>
+              ))}
+            </Select>
+          </FormControl>
+        </div>
+        <Button color="secondary" variant="contained" className="new-competition-button">
+          Ny Tävling
+        </Button>
+      </div>
+      <TableContainer component={Paper}>
+        <Table className={classes.table} aria-label="simple table">
+          <TableHead>
+            <TableRow>
+              <TableCell>Namn</TableCell>
+              <TableCell align="right">Region</TableCell>
+              <TableCell align="right">Ã…r</TableCell>
+              <TableCell align="right"></TableCell>
+            </TableRow>
+          </TableHead>
+          <TableBody>
+            {competitions
+              .filter((row) => {
+                const yearOkay = year == yearInitialValue || row.year == year
+                const regionOkay = region == regionInitialValue || row.region == region
+                return yearOkay && regionOkay
+              })
+              .map((row) => (
+                <TableRow key={row.name}>
+                  <TableCell scope="row">
+                    <Button color="primary" className="aa" component={Link} to="/123">
+                      {row.name}
+                    </Button>
+                  </TableCell>
+                  <TableCell align="right">{row.region}</TableCell>
+                  <TableCell align="right">{row.year}</TableCell>
+                  <TableCell align="right">
+                    <Button onClick={handleClick}>
+                      <MoreHorizIcon />
+                    </Button>
+                  </TableCell>
+                </TableRow>
+              ))}
+          </TableBody>
+        </Table>
+      </TableContainer>
+      <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
+        <MenuItem onClick={handleClose}>Starta</MenuItem>
+        <MenuItem onClick={handleClose}>Duplicera</MenuItem>
+        <MenuItem onClick={handleClose}>Ta bort</MenuItem>
+      </Menu>
+    </div>
+  )
+}
+
+export default CompetitionManager
diff --git a/client/src/components/Regions.test.tsx b/client/src/pages/admin/components/Regions.test.tsx
similarity index 100%
rename from client/src/components/Regions.test.tsx
rename to client/src/pages/admin/components/Regions.test.tsx
diff --git a/client/src/components/Regions.tsx b/client/src/pages/admin/components/Regions.tsx
similarity index 100%
rename from client/src/components/Regions.tsx
rename to client/src/pages/admin/components/Regions.tsx
diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx
index 5498ff8c..c6c65d24 100644
--- a/client/src/pages/views/JudgeViewPage.tsx
+++ b/client/src/pages/views/JudgeViewPage.tsx
@@ -3,6 +3,7 @@ import './ViewSelectPage.css'
 
 const JudgeViewPage: React.FC = (props) => {
   return <div>Judge</div>
+  return <div>Judge</div>
 }
 
 export default JudgeViewPage
diff --git a/client/src/pages/views/ViewSelectPage.css b/client/src/pages/views/ViewSelectPage.css
index 3a684a19..920c6a34 100644
--- a/client/src/pages/views/ViewSelectPage.css
+++ b/client/src/pages/views/ViewSelectPage.css
@@ -1,12 +1,11 @@
-html,
-body {
+.root {
     display: flex;
     justify-content: center;
-    margin-top: 15%;
+    margin-top: 12%;
     height: 100%;
 }
 
-.root {
+.button-group {
     display: flex;
     flex-direction: column;
     justify-content: space-between;
diff --git a/client/src/pages/views/ViewSelectPage.tsx b/client/src/pages/views/ViewSelectPage.tsx
index 249dd87c..81decf7c 100644
--- a/client/src/pages/views/ViewSelectPage.tsx
+++ b/client/src/pages/views/ViewSelectPage.tsx
@@ -7,15 +7,17 @@ const ViewSelectPage: React.FC = (props) => {
   const { path, url } = useRouteMatch()
   return (
     <div className="root">
-      <Button className="view-button" color="primary" variant="contained" component={Link} to={`${url}/participant`}>
-        Deltagarvy
-      </Button>
-      <Button className="view-button" color="primary" variant="contained" component={Link} to={`${url}/audience`}>
-        Åskådarvy
-      </Button>
-      <Button className="view-button" color="primary" variant="contained" component={Link} to={`${url}/judge`}>
-        Domarvy
-      </Button>
+      <div className="button-group">
+        <Button className="view-button" color="primary" variant="contained" component={Link} to={`${url}/participant`}>
+          Deltagarvy
+        </Button>
+        <Button className="view-button" color="primary" variant="contained" component={Link} to={`${url}/audience`}>
+          Åskådarvy
+        </Button>
+        <Button className="view-button" color="primary" variant="contained" component={Link} to={`${url}/judge`}>
+          Domarvy
+        </Button>
+      </div>
     </div>
   )
 }
-- 
GitLab


From 75ab5952ff1f8d05e4f3317cebaa2be476c9c417 Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Thu, 4 Mar 2021 17:07:19 +0100
Subject: [PATCH 07/10] Fix login page, theme color and competition search

---
 client/src/App.tsx                            |  16 +-
 client/src/Main.tsx                           |   4 +-
 client/src/pages/LoginPage.tsx                | 197 ------------------
 client/src/pages/admin/AdminPage.css          |   1 -
 .../admin/components/CompetitionManager.css   |   8 +-
 .../admin/components/CompetitionManager.tsx   |  47 +++--
 client/src/pages/{ => login}/LoginPage.css    |   0
 .../src/pages/{ => login}/LoginPage.test.tsx  |   0
 client/src/pages/login/LoginPage.tsx          |  47 +++++
 .../src/pages/login/components/AdminLogin.tsx |  94 +++++++++
 .../login/components/CompetitionLogin.tsx     |  85 ++++++++
 .../PresentationEditorPage.tsx                |  14 ++
 client/src/pages/views/JudgeViewPage.tsx      |   2 -
 13 files changed, 291 insertions(+), 224 deletions(-)
 delete mode 100644 client/src/pages/LoginPage.tsx
 rename client/src/pages/{ => login}/LoginPage.css (100%)
 rename client/src/pages/{ => login}/LoginPage.test.tsx (100%)
 create mode 100644 client/src/pages/login/LoginPage.tsx
 create mode 100644 client/src/pages/login/components/AdminLogin.tsx
 create mode 100644 client/src/pages/login/components/CompetitionLogin.tsx
 create mode 100644 client/src/pages/presentationEditor/PresentationEditorPage.tsx

diff --git a/client/src/App.tsx b/client/src/App.tsx
index 105a44d5..c488843c 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -1,12 +1,24 @@
+import { createMuiTheme, ThemeProvider } from '@material-ui/core'
 import React from 'react'
 import './App.css'
 import Main from './Main'
 
+const theme = createMuiTheme({
+  palette: {
+    primary: {
+      // Purple and green play nicely together.
+      main: '#6200EE',
+    },
+  },
+})
+
 const App: React.FC = () => {
   return (
     <div className="wrapper">
-      <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
-      <Main />
+      <ThemeProvider theme={theme}>
+        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
+        <Main />
+      </ThemeProvider>
     </div>
   )
 }
diff --git a/client/src/Main.tsx b/client/src/Main.tsx
index 0bae997f..5ad533a6 100644
--- a/client/src/Main.tsx
+++ b/client/src/Main.tsx
@@ -1,7 +1,8 @@
 import React from 'react'
 import { BrowserRouter, Route, Switch } from 'react-router-dom'
 import AdminPage from './pages/admin/AdminPage'
-import LoginPage from './pages/LoginPage'
+import LoginPage from './pages/login/LoginPage'
+import PresentationEditorPage from './pages/presentationEditor/PresentationEditorPage'
 import AudienceViewPage from './pages/views/AudienceViewPage'
 import JudgeViewPage from './pages/views/JudgeViewPage'
 import ParticipantViewPage from './pages/views/ParticipantViewPage'
@@ -13,6 +14,7 @@ const Main = () => {
       <Switch>
         <Route exact path="/" component={LoginPage} />
         <Route path="/admin" component={AdminPage} />
+        <Route path="/competition-id=:id" component={PresentationEditorPage} />
         <Route exact path="/view" component={ViewSelectPage} />
         <Route exact path="/view/participant" component={ParticipantViewPage} />
         <Route exact path="/view/judge" component={JudgeViewPage} />
diff --git a/client/src/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx
deleted file mode 100644
index d2fc3848..00000000
--- a/client/src/pages/LoginPage.tsx
+++ /dev/null
@@ -1,197 +0,0 @@
-import { AppBar, Button, Tab, Tabs, TextField } from '@material-ui/core'
-import { makeStyles, Theme, withStyles } from '@material-ui/core/styles'
-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 { AccountLoginModel, CompetitionLoginModel } from '../interfaces/models'
-import './LoginPage.css'
-const styles = {}
-
-interface AccountLoginFormModel {
-  model: AccountLoginModel
-  error?: string
-}
-
-interface CompetitionLoginFormModel {
-  model: CompetitionLoginModel
-  error?: string
-}
-
-interface ServerResponse {
-  code: number
-  message: string
-}
-
-const accountSchema: Yup.SchemaOf<AccountLoginFormModel> = Yup.object({
-  model: Yup.object()
-    .shape({
-      email: Yup.string().email('Email inte giltig').required('Email krävs'),
-      password: Yup.string().required('Lösenord krävs').min(6, 'Lösenord måste vara minst 6 tecken'),
-    })
-    .required(),
-  error: Yup.string().optional(),
-})
-
-const competitionSchema: Yup.SchemaOf<CompetitionLoginFormModel> = Yup.object({
-  model: Yup.object()
-    .shape({
-      code: Yup.string().required('Mata in kod').min(6, 'Koden måste vara minst 6 tecken'),
-    })
-    .required(),
-  error: Yup.string().optional(),
-})
-
-const handleAccountSubmit = async (values: AccountLoginFormModel, actions: FormikHelpers<AccountLoginFormModel>) => {
-  await axios
-    .post<ServerResponse>(`users/login`, values.model)
-    .then((res) => {
-      actions.resetForm()
-    })
-    .catch(({ response }) => {
-      actions.setFieldError('error', response.data.message)
-    })
-    .finally(() => {
-      actions.setSubmitting(false)
-    })
-}
-
-const handleCompetitionSubmit = async (
-  values: CompetitionLoginFormModel,
-  actions: FormikHelpers<CompetitionLoginFormModel>
-) => {
-  console.log(values.model)
-  await axios
-    .post<ServerResponse>(`users/login`, { code: values.model.code })
-    .then((res) => {
-      actions.resetForm()
-    })
-    .catch(({ response }) => {
-      actions.setFieldError('error', response.data.message)
-    })
-    .finally(() => {
-      actions.setSubmitting(false)
-    })
-}
-
-interface TabPanelProps {
-  activeTab: number
-}
-
-function LoginTab(props: TabPanelProps) {
-  const accountInitialValues: AccountLoginFormModel = {
-    model: { email: '', password: '' },
-  }
-  const competitionInitialValues: CompetitionLoginFormModel = {
-    model: { code: '' },
-  }
-  const { activeTab } = props
-  if (activeTab === 0) {
-    return (
-      <Formik initialValues={accountInitialValues} validationSchema={accountSchema} onSubmit={handleAccountSubmit}>
-        {(formik) => (
-          <form onSubmit={formik.handleSubmit} className="login-form">
-            <TextField
-              label="Email Adress"
-              name="model.email"
-              helperText={formik.touched.model?.email ? formik.errors.model?.email : ''}
-              error={Boolean(formik.touched.model?.email && formik.errors.model?.email)}
-              onChange={formik.handleChange}
-              onBlur={formik.handleBlur}
-              margin="normal"
-            />
-            <TextField
-              label="Lösenord"
-              name="model.password"
-              type="password"
-              helperText={formik.touched.model?.password ? formik.errors.model?.password : ''}
-              error={Boolean(formik.touched.model?.password && formik.errors.model?.password)}
-              onChange={formik.handleChange}
-              onBlur={formik.handleBlur}
-              margin="normal"
-            />
-            <Button
-              type="submit"
-              fullWidth
-              variant="contained"
-              color="primary"
-              disabled={!formik.isValid || !formik.touched.model?.email || !formik.touched.model?.email}
-            >
-              Logga in
-            </Button>
-            {formik.errors.error ? (
-              <Alert severity="error">
-                <AlertTitle>Error</AlertTitle>
-                {formik.errors.error}
-              </Alert>
-            ) : (
-              <div />
-            )}
-          </form>
-        )}
-      </Formik>
-    )
-  }
-  return (
-    <Formik
-      initialValues={competitionInitialValues}
-      validationSchema={competitionSchema}
-      onSubmit={handleCompetitionSubmit}
-    >
-      {(formik) => (
-        <form onSubmit={formik.handleSubmit} className="login-form">
-          <TextField
-            label="Tävlingskod"
-            name="model.code"
-            helperText={formik.touched.model?.code && formik.touched.model?.code ? formik.errors.model?.code : ''}
-            error={Boolean(formik.touched.model?.code && formik.errors.model?.code)}
-            onChange={formik.handleChange}
-            onBlur={formik.handleBlur}
-            margin="normal"
-          />
-          <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
-            Anslut till tävling
-          </Button>
-          {formik.errors.error ? (
-            <Alert severity="error">
-              <AlertTitle>Error</AlertTitle>
-              {formik.errors.error}
-            </Alert>
-          ) : (
-            <div />
-          )}
-        </form>
-      )}
-    </Formik>
-  )
-}
-
-const useStyles = makeStyles((theme: Theme) => ({
-  root: {
-    backgroundColor: theme.palette.background.paper,
-  },
-}))
-
-const LoginPage: React.FC = (props) => {
-  const classes = useStyles()
-  const [loginTab, setLoginTab] = React.useState(0)
-  return (
-    <div className="login-page">
-      <div className={classes.root}>
-        <AppBar position="static">
-          <Tabs value={loginTab} onChange={(event, selectedTab) => setLoginTab(selectedTab)}>
-            <Tab label="Konto" id="simple-tab-0" />
-            <Tab label="Tävling" id="simple-tab-1" />
-          </Tabs>
-        </AppBar>
-        <LoginTab activeTab={loginTab} />
-      </div>
-    </div>
-  )
-}
-
-export default withStyles(styles)(LoginPage)
-
-// TODO: Values carry over from email address to code and vice versa
-// TODO: Errors in email address causes error messages in password field
diff --git a/client/src/pages/admin/AdminPage.css b/client/src/pages/admin/AdminPage.css
index a310f123..471dc7d3 100644
--- a/client/src/pages/admin/AdminPage.css
+++ b/client/src/pages/admin/AdminPage.css
@@ -1,5 +1,4 @@
 .background {
-  background: linear-gradient(to top, #efd5ff 0%, #3d55b3 100%);
   height: 100%;
 }
 
diff --git a/client/src/pages/admin/components/CompetitionManager.css b/client/src/pages/admin/components/CompetitionManager.css
index 51817660..716dde28 100644
--- a/client/src/pages/admin/components/CompetitionManager.css
+++ b/client/src/pages/admin/components/CompetitionManager.css
@@ -1,9 +1,13 @@
 .top-bar {
   display: flex;
   justify-content: space-between;
-  align-items: center;
+  align-items:flex-end;
 }
 
 .new-competition-button {
-  margin-bottom: 15px;
+  margin-bottom: 8px !important;
 }
+
+.remove-competition {
+  color:red !important;
+}
\ No newline at end of file
diff --git a/client/src/pages/admin/components/CompetitionManager.tsx b/client/src/pages/admin/components/CompetitionManager.tsx
index 160b32e4..b2a717e3 100644
--- a/client/src/pages/admin/components/CompetitionManager.tsx
+++ b/client/src/pages/admin/components/CompetitionManager.tsx
@@ -41,24 +41,24 @@ const BootstrapInput = withStyles((theme: Theme) =>
   })
 )(InputBase)
 
-function createCompetition(name: string, region: string, year: number) {
-  return { name, region, year }
+function createCompetition(name: string, region: string, year: number, id: number) {
+  return { name, region, year, id }
 }
 
 const competitions = [
-  createCompetition('Tävling 1', 'Stockholm', 2021),
-  createCompetition('Tävling 2', 'Stockholm', 2020),
-  createCompetition('Tävling 3', 'Sala', 2020),
-  createCompetition('Tävling 4', 'Sundsvall', 2020),
-  createCompetition('Tävling 5', 'Linköping', 2020),
-  createCompetition('Tävling 6', 'Linköping', 2020),
-  createCompetition('Tävling 7', 'Sala', 2019),
-  createCompetition('Tävling 8', 'Stockholm', 2019),
-  createCompetition('Tävling 9', 'Stockholm', 2019),
-  createCompetition('Tävling 10', 'Lidköping', 2019),
-  createCompetition('Tävling 11', 'Stockholm', 2019),
-  createCompetition('Tävling 12', 'Sala', 2018),
-  createCompetition('Tävling 13', 'Tornby', 2018),
+  createCompetition('Tävling 1', 'Stockholm', 2021, 1),
+  createCompetition('Tävling 2', 'Stockholm', 2020, 2),
+  createCompetition('Tävling 3', 'Sala', 2020, 3),
+  createCompetition('Tävling 4', 'Sundsvall', 2020, 4),
+  createCompetition('Tävling 5', 'Linköping', 2020, 5),
+  createCompetition('Tävling 6', 'Linköping', 2020, 6),
+  createCompetition('Tävling 7', 'Sala', 2019, 7),
+  createCompetition('Tävling 8', 'Stockholm', 2019, 8),
+  createCompetition('Tävling 9', 'Stockholm', 2019, 9),
+  createCompetition('Tävling 10', 'Lidköping', 2019, 10),
+  createCompetition('Tävling 11', 'Stockholm', 2019, 11),
+  createCompetition('Tävling 12', 'Sala', 2018, 12),
+  createCompetition('Tävling 13', 'Tornby', 2018, 13),
 ]
 
 const regions = competitions
@@ -86,6 +86,7 @@ const CompetitionManager: React.FC = (props) => {
   const yearInitialValue = 0
   const regionInitialValue = ''
   const noFilterText = 'Alla'
+  const [searchInput, setSearchInput] = React.useState('')
   const [year, setYear] = React.useState(yearInitialValue)
   const [region, setRegion] = React.useState(regionInitialValue)
   const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
@@ -95,6 +96,11 @@ const CompetitionManager: React.FC = (props) => {
   const handleClose = () => {
     setAnchorEl(null)
   }
+
+  const onSearchChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+    setSearchInput(event.target.value)
+  }
+
   return (
     <div>
       <div className="top-bar">
@@ -103,7 +109,7 @@ const CompetitionManager: React.FC = (props) => {
             <InputLabel shrink id="demo-customized-textbox">
               Sök
             </InputLabel>
-            <BootstrapInput id="demo-customized-textbox" />
+            <BootstrapInput id="demo-customized-textbox" onChange={onSearchChange} />
           </FormControl>
           <FormControl className={classes.margin}>
             <InputLabel shrink id="demo-customized-select-native">
@@ -162,14 +168,15 @@ const CompetitionManager: React.FC = (props) => {
           <TableBody>
             {competitions
               .filter((row) => {
+                const nameOkay = row.name.match(RegExp(searchInput, 'i')) //Makes sure name matches search input case insensitively
                 const yearOkay = year == yearInitialValue || row.year == year
                 const regionOkay = region == regionInitialValue || row.region == region
-                return yearOkay && regionOkay
+                return yearOkay && regionOkay && nameOkay
               })
               .map((row) => (
                 <TableRow key={row.name}>
                   <TableCell scope="row">
-                    <Button color="primary" className="aa" component={Link} to="/123">
+                    <Button color="primary" component={Link} to={`/competition-id=${row.id}`}>
                       {row.name}
                     </Button>
                   </TableCell>
@@ -188,7 +195,9 @@ const CompetitionManager: React.FC = (props) => {
       <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
         <MenuItem onClick={handleClose}>Starta</MenuItem>
         <MenuItem onClick={handleClose}>Duplicera</MenuItem>
-        <MenuItem onClick={handleClose}>Ta bort</MenuItem>
+        <MenuItem className="remove-competition" onClick={handleClose}>
+          Ta bort
+        </MenuItem>
       </Menu>
     </div>
   )
diff --git a/client/src/pages/LoginPage.css b/client/src/pages/login/LoginPage.css
similarity index 100%
rename from client/src/pages/LoginPage.css
rename to client/src/pages/login/LoginPage.css
diff --git a/client/src/pages/LoginPage.test.tsx b/client/src/pages/login/LoginPage.test.tsx
similarity index 100%
rename from client/src/pages/LoginPage.test.tsx
rename to client/src/pages/login/LoginPage.test.tsx
diff --git a/client/src/pages/login/LoginPage.tsx b/client/src/pages/login/LoginPage.tsx
new file mode 100644
index 00000000..abb2e181
--- /dev/null
+++ b/client/src/pages/login/LoginPage.tsx
@@ -0,0 +1,47 @@
+import { AppBar, Tab, Tabs } from '@material-ui/core'
+import { makeStyles, Theme } from '@material-ui/core/styles'
+import React from 'react'
+import AdminLogin from './components/AdminLogin'
+import CompetitionLogin from './components/CompetitionLogin'
+import './LoginPage.css'
+
+interface TabPanelProps {
+  activeTab: number
+}
+
+function LoginContent(props: TabPanelProps) {
+  const { activeTab } = props
+  if (activeTab === 0) {
+    return <AdminLogin />
+  }
+  return <CompetitionLogin />
+}
+
+const useStyles = makeStyles((theme: Theme) => ({
+  root: {
+    backgroundColor: theme.palette.background.paper,
+  },
+}))
+
+const LoginPage: React.FC = (props) => {
+  const classes = useStyles()
+  const [loginTab, setLoginTab] = React.useState(0)
+  return (
+    <div className="login-page">
+      <div className={classes.root}>
+        <AppBar position="static">
+          <Tabs value={loginTab} onChange={(event, selectedTab) => setLoginTab(selectedTab)}>
+            <Tab label="Konto" id="simple-tab-0" />
+            <Tab label="Tävling" id="simple-tab-1" />
+          </Tabs>
+        </AppBar>
+        <LoginContent activeTab={loginTab} />
+      </div>
+    </div>
+  )
+}
+
+export default LoginPage
+
+// TODO: Values carry over from email address to code and vice versa
+// TODO: Errors in email address causes error messages in password field
diff --git a/client/src/pages/login/components/AdminLogin.tsx b/client/src/pages/login/components/AdminLogin.tsx
new file mode 100644
index 00000000..77dc2a4c
--- /dev/null
+++ b/client/src/pages/login/components/AdminLogin.tsx
@@ -0,0 +1,94 @@
+import { Button, TextField } from '@material-ui/core'
+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 { AccountLoginModel } from '../../../interfaces/models'
+
+interface AccountLoginFormModel {
+  model: AccountLoginModel
+  error?: string
+}
+
+interface ServerResponse {
+  code: number
+  message: string
+}
+
+const accountSchema: Yup.SchemaOf<AccountLoginFormModel> = Yup.object({
+  model: Yup.object()
+    .shape({
+      email: Yup.string().email('Email inte giltig').required('Email krävs'),
+      password: Yup.string().required('Lösenord krävs').min(6, 'Lösenord måste vara minst 6 tecken'),
+    })
+    .required(),
+  error: Yup.string().optional(),
+})
+
+const handleAccountSubmit = async (values: AccountLoginFormModel, actions: FormikHelpers<AccountLoginFormModel>) => {
+  await axios
+    .post<ServerResponse>(`users/login`, values.model)
+    .then((res) => {
+      actions.resetForm()
+    })
+    .catch(({ response }) => {
+      console.log(response.data.message)
+      actions.setFieldError('error', response.data.message)
+    })
+    .finally(() => {
+      actions.setSubmitting(false)
+    })
+}
+
+const AdminLogin: React.FC = (props) => {
+  const accountInitialValues: AccountLoginFormModel = {
+    model: { email: '', password: '' },
+  }
+  return (
+    <Formik initialValues={accountInitialValues} validationSchema={accountSchema} onSubmit={handleAccountSubmit}>
+      {(formik) => (
+        <form onSubmit={formik.handleSubmit} className="login-form">
+          <TextField
+            label="Email Adress"
+            name="model.email"
+            helperText={formik.touched.model?.email ? formik.errors.model?.email : ''}
+            error={Boolean(formik.touched.model?.email && formik.errors.model?.email)}
+            onChange={formik.handleChange}
+            onBlur={formik.handleBlur}
+            margin="normal"
+          />
+          <TextField
+            label="Lösenord"
+            name="model.password"
+            type="password"
+            helperText={formik.touched.model?.password ? formik.errors.model?.password : ''}
+            error={Boolean(formik.touched.model?.password && formik.errors.model?.password)}
+            onChange={formik.handleChange}
+            onBlur={formik.handleBlur}
+            margin="normal"
+          />
+          <Button
+            type="submit"
+            fullWidth
+            variant="contained"
+            color="primary"
+            disabled={!formik.isValid || !formik.touched.model?.email || !formik.touched.model?.email}
+          >
+            Logga in
+          </Button>
+          {formik.errors.error ? (
+            <Alert severity="error">
+              <AlertTitle>Error</AlertTitle>
+              {formik.errors.error}
+            </Alert>
+          ) : (
+            <div />
+          )}
+        </form>
+      )}
+    </Formik>
+  )
+}
+
+export default AdminLogin
diff --git a/client/src/pages/login/components/CompetitionLogin.tsx b/client/src/pages/login/components/CompetitionLogin.tsx
new file mode 100644
index 00000000..2450196d
--- /dev/null
+++ b/client/src/pages/login/components/CompetitionLogin.tsx
@@ -0,0 +1,85 @@
+import { Button, TextField } from '@material-ui/core'
+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 { CompetitionLoginModel } from '../../../interfaces/models'
+
+interface CompetitionLoginFormModel {
+  model: CompetitionLoginModel
+  error?: string
+}
+
+interface ServerResponse {
+  code: number
+  message: string
+}
+
+const competitionSchema: Yup.SchemaOf<CompetitionLoginFormModel> = Yup.object({
+  model: Yup.object()
+    .shape({
+      code: Yup.string().required('Mata in kod').min(6, 'Koden måste vara minst 6 tecken'),
+    })
+    .required(),
+  error: Yup.string().optional(),
+})
+
+const handleCompetitionSubmit = async (
+  values: CompetitionLoginFormModel,
+  actions: FormikHelpers<CompetitionLoginFormModel>
+) => {
+  console.log(values.model)
+  await axios
+    .post<ServerResponse>(`users/login`, { code: values.model.code })
+    .then((res) => {
+      actions.resetForm()
+    })
+    .catch(({ response }) => {
+      console.log(response.data.message)
+      actions.setFieldError('error', response.data.message)
+    })
+    .finally(() => {
+      actions.setSubmitting(false)
+    })
+}
+
+const CompetitionLogin: React.FC = (props) => {
+  const competitionInitialValues: CompetitionLoginFormModel = {
+    model: { code: '' },
+  }
+  return (
+    <Formik
+      initialValues={competitionInitialValues}
+      validationSchema={competitionSchema}
+      onSubmit={handleCompetitionSubmit}
+    >
+      {(formik) => (
+        <form onSubmit={formik.handleSubmit} className="login-form">
+          <TextField
+            label="Tävlingskod"
+            name="model.code"
+            helperText={formik.touched.model?.code && formik.touched.model?.code ? formik.errors.model?.code : ''}
+            error={Boolean(formik.touched.model?.code && formik.errors.model?.code)}
+            onChange={formik.handleChange}
+            onBlur={formik.handleBlur}
+            margin="normal"
+          />
+          <Button type="submit" fullWidth variant="contained" color="primary" disabled={!formik.isValid}>
+            Anslut till tävling
+          </Button>
+          {formik.errors.error ? (
+            <Alert severity="error">
+              <AlertTitle>Error</AlertTitle>
+              {formik.errors.error}
+            </Alert>
+          ) : (
+            <div />
+          )}
+        </form>
+      )}
+    </Formik>
+  )
+}
+
+export default CompetitionLogin
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
new file mode 100644
index 00000000..e5f1366e
--- /dev/null
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
@@ -0,0 +1,14 @@
+import { Typography } from '@material-ui/core'
+import React from 'react'
+import { useParams } from 'react-router-dom'
+
+interface CompetitionParams {
+  id: string
+}
+
+const PresentationEditorPage: React.FC = (props) => {
+  const params: CompetitionParams = useParams()
+  return <Typography variant="h1">tävling: {params.id}</Typography>
+}
+
+export default PresentationEditorPage
diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx
index c6c65d24..0051152b 100644
--- a/client/src/pages/views/JudgeViewPage.tsx
+++ b/client/src/pages/views/JudgeViewPage.tsx
@@ -1,9 +1,7 @@
 import React from 'react'
-import './ViewSelectPage.css'
 
 const JudgeViewPage: React.FC = (props) => {
   return <div>Judge</div>
-  return <div>Judge</div>
 }
 
 export default JudgeViewPage
-- 
GitLab


From 1b1ee897b955befbff03bb36515fc5aa74f7092e Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Thu, 4 Mar 2021 17:09:25 +0100
Subject: [PATCH 08/10] remove todo

---
 client/src/pages/login/LoginPage.tsx | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/client/src/pages/login/LoginPage.tsx b/client/src/pages/login/LoginPage.tsx
index abb2e181..776e64b1 100644
--- a/client/src/pages/login/LoginPage.tsx
+++ b/client/src/pages/login/LoginPage.tsx
@@ -42,6 +42,3 @@ const LoginPage: React.FC = (props) => {
 }
 
 export default LoginPage
-
-// TODO: Values carry over from email address to code and vice versa
-// TODO: Errors in email address causes error messages in password field
-- 
GitLab


From abe2d723648381db4e131ba28df49795928c67c1 Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Thu, 4 Mar 2021 17:44:31 +0100
Subject: [PATCH 09/10] fix tests not compiling

---
 .../src/pages/admin/components/CompetitionManager.test.tsx | 7 ++++++-
 client/src/pages/login/components/AdminLogin.test.tsx      | 7 +++++++
 .../src/pages/login/components/CompetitionLogin.test.tsx   | 7 +++++++
 3 files changed, 20 insertions(+), 1 deletion(-)
 create mode 100644 client/src/pages/login/components/AdminLogin.test.tsx
 create mode 100644 client/src/pages/login/components/CompetitionLogin.test.tsx

diff --git a/client/src/pages/admin/components/CompetitionManager.test.tsx b/client/src/pages/admin/components/CompetitionManager.test.tsx
index b7156f68..2a92138f 100644
--- a/client/src/pages/admin/components/CompetitionManager.test.tsx
+++ b/client/src/pages/admin/components/CompetitionManager.test.tsx
@@ -1,7 +1,12 @@
 import { render } from '@testing-library/react'
 import React from 'react'
+import { BrowserRouter } from 'react-router-dom'
 import CompetitionManager from './CompetitionManager'
 
 it('renders competition manager', () => {
-  render(<CompetitionManager />)
+  render(
+    <BrowserRouter>
+      <CompetitionManager />
+    </BrowserRouter>
+  )
 })
diff --git a/client/src/pages/login/components/AdminLogin.test.tsx b/client/src/pages/login/components/AdminLogin.test.tsx
new file mode 100644
index 00000000..4e966c89
--- /dev/null
+++ b/client/src/pages/login/components/AdminLogin.test.tsx
@@ -0,0 +1,7 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import AdminLogin from './AdminLogin'
+
+it('renders admin login', () => {
+  render(<AdminLogin />)
+})
diff --git a/client/src/pages/login/components/CompetitionLogin.test.tsx b/client/src/pages/login/components/CompetitionLogin.test.tsx
new file mode 100644
index 00000000..29213c94
--- /dev/null
+++ b/client/src/pages/login/components/CompetitionLogin.test.tsx
@@ -0,0 +1,7 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import CompetitionLogin from './CompetitionLogin'
+
+it('renders competition login', () => {
+  render(<CompetitionLogin />)
+})
-- 
GitLab


From af44ab0b11ec8c2e42564c683caf0dad9611541c Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Mon, 8 Mar 2021 08:30:01 +0100
Subject: [PATCH 10/10] Added structure for presentation editor

---
 client/src/App.tsx                            |   5 +-
 client/src/Main.tsx                           |   2 +-
 .../admin/components/CompetitionManager.tsx   |   2 +-
 .../PresentationEditorPage.css                |  23 ++++
 .../PresentationEditorPage.tsx                | 118 +++++++++++++++++-
 .../components/CompetitionSettings.tsx        |  70 +++++++++++
 .../components/SettingsPanel.tsx              |  36 ++++++
 7 files changed, 251 insertions(+), 5 deletions(-)
 create mode 100644 client/src/pages/presentationEditor/PresentationEditorPage.css
 create mode 100644 client/src/pages/presentationEditor/components/CompetitionSettings.tsx
 create mode 100644 client/src/pages/presentationEditor/components/SettingsPanel.tsx

diff --git a/client/src/App.tsx b/client/src/App.tsx
index c488843c..c80013fd 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -1,4 +1,5 @@
 import { createMuiTheme, ThemeProvider } from '@material-ui/core'
+import { teal } from '@material-ui/core/colors'
 import React from 'react'
 import './App.css'
 import Main from './Main'
@@ -6,9 +7,11 @@ import Main from './Main'
 const theme = createMuiTheme({
   palette: {
     primary: {
-      // Purple and green play nicely together.
       main: '#6200EE',
     },
+    secondary: {
+      main: teal.A400,
+    },
   },
 })
 
diff --git a/client/src/Main.tsx b/client/src/Main.tsx
index 5ad533a6..568d1010 100644
--- a/client/src/Main.tsx
+++ b/client/src/Main.tsx
@@ -14,7 +14,7 @@ const Main = () => {
       <Switch>
         <Route exact path="/" component={LoginPage} />
         <Route path="/admin" component={AdminPage} />
-        <Route path="/competition-id=:id" component={PresentationEditorPage} />
+        <Route path="/editor/competition-id=:id" component={PresentationEditorPage} />
         <Route exact path="/view" component={ViewSelectPage} />
         <Route exact path="/view/participant" component={ParticipantViewPage} />
         <Route exact path="/view/judge" component={JudgeViewPage} />
diff --git a/client/src/pages/admin/components/CompetitionManager.tsx b/client/src/pages/admin/components/CompetitionManager.tsx
index b2a717e3..a85378ab 100644
--- a/client/src/pages/admin/components/CompetitionManager.tsx
+++ b/client/src/pages/admin/components/CompetitionManager.tsx
@@ -176,7 +176,7 @@ const CompetitionManager: React.FC = (props) => {
               .map((row) => (
                 <TableRow key={row.name}>
                   <TableCell scope="row">
-                    <Button color="primary" component={Link} to={`/competition-id=${row.id}`}>
+                    <Button color="primary" component={Link} to={`/editor/competition-id=${row.id}`}>
                       {row.name}
                     </Button>
                   </TableCell>
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.css b/client/src/pages/presentationEditor/PresentationEditorPage.css
new file mode 100644
index 00000000..2d3d142b
--- /dev/null
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.css
@@ -0,0 +1,23 @@
+.toolbar-container {
+  display:flex;
+  justify-content: space-between;
+}
+
+.view-button {
+  margin-right: 8px;
+}
+
+.view-button-group {
+  display: flex;
+  flex-direction: row;
+}
+
+.slide-list-item {
+  text-align: center !important;
+  height: 60px;
+}
+
+.right-drawer-tab {
+  height: 64px;
+  min-width: 130px !important;
+}
\ No newline at end of file
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
index e5f1366e..c48e8958 100644
--- a/client/src/pages/presentationEditor/PresentationEditorPage.tsx
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
@@ -1,14 +1,128 @@
-import { Typography } from '@material-ui/core'
+import { Button, Divider, Typography } from '@material-ui/core'
+import AppBar from '@material-ui/core/AppBar'
+import CssBaseline from '@material-ui/core/CssBaseline'
+import Drawer from '@material-ui/core/Drawer'
+import List from '@material-ui/core/List'
+import ListItem from '@material-ui/core/ListItem'
+import ListItemText from '@material-ui/core/ListItemText'
+import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
+import Toolbar from '@material-ui/core/Toolbar'
 import React from 'react'
 import { useParams } from 'react-router-dom'
+import SettingsPanel from './components/SettingsPanel'
+import './PresentationEditorPage.css'
+
+function createSlide(name: string) {
+  return { name }
+}
+
+const slides = [
+  createSlide('Sida 1'),
+  createSlide('Sida 2'),
+  createSlide('Sida 3'),
+  createSlide('Sida 4'),
+  createSlide('Sida 5'),
+  createSlide('Sida 6'),
+  createSlide('Sida 7'),
+]
+const leftDrawerWidth = 150
+const rightDrawerWidth = 390
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    root: {
+      display: 'flex',
+    },
+    appBar: {
+      width: `calc(100% - ${rightDrawerWidth}px)`,
+      marginLeft: leftDrawerWidth,
+      marginRight: rightDrawerWidth,
+    },
+    leftDrawer: {
+      width: leftDrawerWidth,
+      flexShrink: 0,
+      position: 'relative',
+      zIndex: 1,
+    },
+    rightDrawer: {
+      width: rightDrawerWidth,
+      flexShrink: 0,
+    },
+    leftDrawerPaper: {
+      width: leftDrawerWidth,
+    },
+    rightDrawerPaper: {
+      width: rightDrawerWidth,
+    },
+    // necessary for content to be below app bar
+    toolbar: theme.mixins.toolbar,
+    content: {
+      flexGrow: 1,
+      backgroundColor: theme.palette.background.default,
+      padding: theme.spacing(3),
+    },
+  })
+)
 
 interface CompetitionParams {
   id: string
 }
 
 const PresentationEditorPage: React.FC = (props) => {
+  const classes = useStyles()
   const params: CompetitionParams = useParams()
-  return <Typography variant="h1">tävling: {params.id}</Typography>
+  return (
+    <div className={classes.root}>
+      <CssBaseline />
+      <AppBar position="fixed" className={classes.appBar}>
+        <Toolbar className="toolbar-container">
+          <Typography variant="h6" noWrap>
+            Tävling nr: {params.id}
+          </Typography>
+          <div className="view-button-group">
+            <Button className="view-button" variant="contained" color="secondary">
+              Åskådarvy
+            </Button>
+            <Button className="view-button" variant="contained" color="secondary">
+              Deltagarvy
+            </Button>
+            <Button className="view-button" variant="contained" color="secondary">
+              Domarvy
+            </Button>
+          </div>
+        </Toolbar>
+      </AppBar>
+      <Drawer
+        className={classes.leftDrawer}
+        variant="permanent"
+        classes={{
+          paper: classes.leftDrawerPaper,
+        }}
+        anchor="left"
+      >
+        <div className={classes.toolbar} />
+        <Divider />
+        <List>
+          {slides.map((slide, index) => (
+            <ListItem className="slide-list-item" divider button key={slide.name}>
+              <ListItemText primary={slide.name} />
+            </ListItem>
+          ))}
+        </List>
+      </Drawer>
+      <div className={classes.toolbar} />
+      <Drawer
+        className={classes.rightDrawer}
+        variant="permanent"
+        classes={{
+          paper: classes.rightDrawerPaper,
+        }}
+        anchor="right"
+      >
+        <SettingsPanel></SettingsPanel>
+      </Drawer>
+    </div>
+  )
 }
 
 export default PresentationEditorPage
diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
new file mode 100644
index 00000000..72478634
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
@@ -0,0 +1,70 @@
+import { Button, Divider, List, ListItem, ListItemText, TextField } from '@material-ui/core'
+import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
+import CloseIcon from '@material-ui/icons/Close'
+import React, { useState } from 'react'
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    textInputContainer: {
+      '& > *': {
+        margin: theme.spacing(1),
+        width: '100%',
+      },
+    },
+    textInput: {
+      margin: theme.spacing(2),
+      width: '87%',
+    },
+    textCenter: {
+      textAlign: 'center',
+    },
+    center: {
+      display: 'flex',
+      justifyContent: 'center',
+    },
+  })
+)
+interface TeamListItemProps {
+  name: string
+}
+
+const CompetitionSettings: React.FC = (props) => {
+  const classes = useStyles()
+  const initialList = [
+    { id: '1', name: 'Lag1' },
+    { id: '2', name: 'Lag2' },
+    { id: '3', name: 'Lag3' },
+  ]
+  const handleClick = (id: string) => {
+    setTeams(teams.filter((item) => item.id !== id)) //Will not be done like this when api is used
+  }
+  const [teams, setTeams] = useState(initialList)
+  return (
+    <div className={classes.textInputContainer}>
+      <form noValidate autoComplete="off">
+        <TextField className={classes.textInput} id="outlined-basic" label="Tävlingsnamn" variant="outlined" />
+        <Divider />
+        <TextField className={classes.textInput} id="outlined-basic" label="Stad" variant="outlined" />
+      </form>
+      <List>
+        <Divider />
+        <ListItem>
+          <ListItemText className={classes.textCenter} primary="Lag" />
+        </ListItem>
+        {teams.map((team) => (
+          <div key={team.id}>
+            <ListItem divider button>
+              <ListItemText primary={team.name} />
+              <CloseIcon onClick={() => handleClick(team.id)} />
+            </ListItem>
+          </div>
+        ))}
+        <ListItem className={classes.center} button>
+          <Button>Lägg till lag</Button>
+        </ListItem>
+      </List>
+    </div>
+  )
+}
+
+export default CompetitionSettings
diff --git a/client/src/pages/presentationEditor/components/SettingsPanel.tsx b/client/src/pages/presentationEditor/components/SettingsPanel.tsx
new file mode 100644
index 00000000..946befe7
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/SettingsPanel.tsx
@@ -0,0 +1,36 @@
+import { Tab, Tabs } from '@material-ui/core'
+import AppBar from '@material-ui/core/AppBar'
+import React from 'react'
+import CompetitionSettings from './CompetitionSettings'
+
+interface TabPanelProps {
+  activeTab: number
+}
+
+function TabContent(props: TabPanelProps) {
+  const { activeTab } = props
+  if (activeTab === 0) {
+    return <CompetitionSettings />
+  } else if (activeTab === 1) {
+    return <div>2</div>
+  }
+  return <div>3</div>
+}
+
+const SettingsPanel: React.FC = (props) => {
+  const [activeTab, setActiveTab] = React.useState(0)
+  return (
+    <div>
+      <AppBar position="static">
+        <Tabs value={activeTab} onChange={(event, val) => setActiveTab(val)} aria-label="simple tabs example">
+          <Tab className="right-drawer-tab" label="Tävling" />
+          <Tab className="right-drawer-tab" label="Sida" />
+          <Tab className="right-drawer-tab" label="Stil" />
+        </Tabs>
+      </AppBar>
+      <TabContent activeTab={activeTab} />
+    </div>
+  )
+}
+
+export default SettingsPanel
-- 
GitLab