From b24f3bba874eea9e4aaa02367132612dd1237e3a Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Fri, 9 Apr 2021 10:13:05 +0000
Subject: [PATCH] Resolve "Add routes and template for Presentation view"

---
 client/.eslintrc                              |  52 ++++-----
 client/src/Main.tsx                           |   8 +-
 client/src/actions/presentation.ts            |  44 ++++++++
 client/src/actions/types.ts                   |   5 +
 client/src/interfaces/Slide.ts                |   7 ++
 client/src/interfaces/Team.ts                 |   4 +
 client/src/interfaces/ViewParams.ts           |   4 +
 client/src/pages/admin/AdminPage.test.tsx     |  37 ++++++
 client/src/pages/admin/AdminPage.tsx          |   2 +-
 .../admin/components/CompetitionManager.tsx   |   5 +-
 .../src/pages/views/AudienceViewPage.test.tsx |   8 +-
 client/src/pages/views/JudgeViewPage.test.tsx |  38 ++++++-
 client/src/pages/views/JudgeViewPage.tsx      | 106 +++++++++++++++++-
 .../pages/views/ParticipantViewPage.test.tsx  |   8 +-
 .../src/pages/views/ParticipantViewPage.tsx   |   3 +-
 .../pages/views/PresenterViewPage.test.tsx    |  43 +++++++
 client/src/pages/views/PresenterViewPage.tsx  |  74 ++++++++++++
 .../views/components/JudgeScoreDisplay.tsx    |  39 +++++++
 .../views/components/SlideDisplay.test.tsx    |  13 +++
 .../pages/views/components/SlideDisplay.tsx   |  15 +++
 client/src/pages/views/components/styled.tsx  |  27 +++++
 client/src/pages/views/styled.tsx             |  74 +++++++++++-
 client/src/reducers/allReducers.ts            |   2 +
 client/src/reducers/presentationReducer.ts    |  72 ++++++++++++
 client/src/styled.tsx                         |   1 -
 25 files changed, 651 insertions(+), 40 deletions(-)
 create mode 100644 client/src/actions/presentation.ts
 create mode 100644 client/src/interfaces/Slide.ts
 create mode 100644 client/src/interfaces/Team.ts
 create mode 100644 client/src/interfaces/ViewParams.ts
 create mode 100644 client/src/pages/views/PresenterViewPage.test.tsx
 create mode 100644 client/src/pages/views/PresenterViewPage.tsx
 create mode 100644 client/src/pages/views/components/JudgeScoreDisplay.tsx
 create mode 100644 client/src/pages/views/components/SlideDisplay.test.tsx
 create mode 100644 client/src/pages/views/components/SlideDisplay.tsx
 create mode 100644 client/src/pages/views/components/styled.tsx
 create mode 100644 client/src/reducers/presentationReducer.ts

diff --git a/client/.eslintrc b/client/.eslintrc
index 3e2dbeb0..d5bbb2ed 100644
--- a/client/.eslintrc
+++ b/client/.eslintrc
@@ -1,29 +1,29 @@
 {
-    "parser": "@typescript-eslint/parser",
-    "parserOptions": {
-        "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", {
-       "endOfLine":"auto"
-     }]
+  "parser": "@typescript-eslint/parser",
+  "parserOptions": {
+    "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",
+      {
+        "endOfLine": "auto"
+      }
+    ]
   }
-  
+}
diff --git a/client/src/Main.tsx b/client/src/Main.tsx
index c5494c88..f32aad9f 100644
--- a/client/src/Main.tsx
+++ b/client/src/Main.tsx
@@ -6,6 +6,7 @@ import PresentationEditorPage from './pages/presentationEditor/PresentationEdito
 import AudienceViewPage from './pages/views/AudienceViewPage'
 import JudgeViewPage from './pages/views/JudgeViewPage'
 import ParticipantViewPage from './pages/views/ParticipantViewPage'
+import PresenterViewPage from './pages/views/PresenterViewPage'
 import ViewSelectPage from './pages/views/ViewSelectPage'
 import SecureRoute from './utils/SecureRoute'
 
@@ -17,9 +18,10 @@ const Main: React.FC = () => {
         <SecureRoute path="/admin" component={AdminPage} />
         <SecureRoute 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} />
-        <Route exact path="/view/audience" component={AudienceViewPage} />
+        <Route exact path="/participant/id=:id&code=:code" component={ParticipantViewPage} />
+        <SecureRoute exact path="/presenter/id=:id&code=:code" component={PresenterViewPage} />
+        <Route exact path="/judge/id=:id&code=:code" component={JudgeViewPage} />
+        <Route exact path="/audience/id=:id&code=:code" component={AudienceViewPage} />
       </Switch>
     </BrowserRouter>
   )
diff --git a/client/src/actions/presentation.ts b/client/src/actions/presentation.ts
new file mode 100644
index 00000000..9e482d2b
--- /dev/null
+++ b/client/src/actions/presentation.ts
@@ -0,0 +1,44 @@
+import axios from 'axios'
+import { Slide } from '../interfaces/Slide'
+import { AppDispatch } from './../store'
+import Types from './types'
+
+export const getPresentationCompetition = (id: string) => async (dispatch: AppDispatch) => {
+  await axios
+    .get(`/competitions/${id}`)
+    .then((res) => {
+      dispatch({
+        type: Types.SET_PRESENTATION_COMPETITION,
+        payload: res.data,
+      })
+    })
+    .catch((err) => {
+      console.log(err)
+    })
+}
+
+export const getPresentationTeams = (id: string) => async (dispatch: AppDispatch) => {
+  await axios
+    .get(`/competitions/${id}/teams`)
+    .then((res) => {
+      dispatch({
+        type: Types.SET_PRESENTATION_TEAMS,
+        payload: res.data.items,
+      })
+    })
+    .catch((err) => {
+      console.log(err)
+    })
+}
+
+export const setCurrentSlide = (slide: Slide) => (dispatch: AppDispatch) => {
+  dispatch({ type: Types.SET_PRESENTATION_SLIDE, payload: slide })
+}
+
+export const setCurrentSlidePrevious = () => (dispatch: AppDispatch) => {
+  dispatch({ type: Types.SET_PRESENTATION_SLIDE_PREVIOUS })
+}
+
+export const setCurrentSlideNext = () => (dispatch: AppDispatch) => {
+  dispatch({ type: Types.SET_PRESENTATION_SLIDE_NEXT })
+}
diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts
index b72a3586..c0b7b629 100644
--- a/client/src/actions/types.ts
+++ b/client/src/actions/types.ts
@@ -15,6 +15,11 @@ export default {
   SET_COMPETITIONS_FILTER_PARAMS: 'SET_COMPETITIONS_FILTER_PARAMS',
   SET_COMPETITIONS_TOTAL: 'SET_COMPETITIONS_TOTAL',
   SET_COMPETITIONS_COUNT: 'SET_COMPETITIONS_COUNT',
+  SET_PRESENTATION_COMPETITION: 'SET_PRESENTATION_COMPETITION',
+  SET_PRESENTATION_SLIDE: 'SET_PRESENTATION_SLIDE',
+  SET_PRESENTATION_SLIDE_PREVIOUS: 'SET_PRESENTATION_SLIDE_PREVIOUS',
+  SET_PRESENTATION_SLIDE_NEXT: 'SET_PRESENTATION_SLIDE_NEXT',
+  SET_PRESENTATION_TEAMS: 'SET_PRESENTATION_TEAMS',
   SET_CITIES: 'SET_CITIES',
   SET_CITIES_TOTAL: 'SET_CITIES_TOTAL',
   SET_CITIES_COUNT: 'SET_CITIES_COUNT',
diff --git a/client/src/interfaces/Slide.ts b/client/src/interfaces/Slide.ts
new file mode 100644
index 00000000..dcdbd366
--- /dev/null
+++ b/client/src/interfaces/Slide.ts
@@ -0,0 +1,7 @@
+export interface Slide {
+  competition_id: number
+  id: number
+  order: number
+  timer: number
+  title: string
+}
diff --git a/client/src/interfaces/Team.ts b/client/src/interfaces/Team.ts
new file mode 100644
index 00000000..10b4350e
--- /dev/null
+++ b/client/src/interfaces/Team.ts
@@ -0,0 +1,4 @@
+export interface Team {
+  id: number
+  name: string
+}
diff --git a/client/src/interfaces/ViewParams.ts b/client/src/interfaces/ViewParams.ts
new file mode 100644
index 00000000..e9aa6a5c
--- /dev/null
+++ b/client/src/interfaces/ViewParams.ts
@@ -0,0 +1,4 @@
+export interface ViewParams {
+  id: string
+  code: string
+}
diff --git a/client/src/pages/admin/AdminPage.test.tsx b/client/src/pages/admin/AdminPage.test.tsx
index efb56bb2..ab694cee 100644
--- a/client/src/pages/admin/AdminPage.test.tsx
+++ b/client/src/pages/admin/AdminPage.test.tsx
@@ -1,4 +1,5 @@
 import { render } from '@testing-library/react'
+import mockedAxios from 'axios'
 import React from 'react'
 import { Provider } from 'react-redux'
 import { BrowserRouter } from 'react-router-dom'
@@ -6,6 +7,42 @@ import store from '../../store'
 import AdminPage from './AdminPage'
 
 it('renders admin view', () => {
+  const cityRes: any = {
+    data: {
+      items: [
+        {
+          id: 1,
+          name: 'Link\u00f6ping',
+        },
+        {
+          id: 2,
+          name: 'Stockholm',
+        },
+      ],
+      count: 2,
+      total_count: 3,
+    },
+  }
+  const rolesRes: any = {
+    data: {
+      items: [
+        {
+          id: 1,
+          name: 'role1',
+        },
+        {
+          id: 2,
+          name: 'role2',
+        },
+      ],
+      count: 2,
+      total_count: 3,
+    },
+  }
+  ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => {
+    if (path === '/misc/cities') return Promise.resolve(cityRes)
+    else return Promise.resolve(rolesRes)
+  })
   render(
     <Provider store={store}>
       <BrowserRouter>
diff --git a/client/src/pages/admin/AdminPage.tsx b/client/src/pages/admin/AdminPage.tsx
index 8b0fb81c..793da84f 100644
--- a/client/src/pages/admin/AdminPage.tsx
+++ b/client/src/pages/admin/AdminPage.tsx
@@ -51,7 +51,7 @@ const useStyles = makeStyles((theme: Theme) =>
     content: {
       flexGrow: 1,
       backgroundColor: theme.palette.background.default,
-      paddingLeft: theme.spacing(30),
+      paddingLeft: theme.spacing(31),
     },
   })
 )
diff --git a/client/src/pages/admin/components/CompetitionManager.tsx b/client/src/pages/admin/components/CompetitionManager.tsx
index 07d7f32d..c271641f 100644
--- a/client/src/pages/admin/components/CompetitionManager.tsx
+++ b/client/src/pages/admin/components/CompetitionManager.tsx
@@ -14,7 +14,7 @@ import TableRow from '@material-ui/core/TableRow'
 import MoreHorizIcon from '@material-ui/icons/MoreHoriz'
 import axios from 'axios'
 import React, { useEffect } from 'react'
-import { Link } from 'react-router-dom'
+import { Link, useHistory } from 'react-router-dom'
 import { getCompetitions, setFilterParams } from '../../../actions/competitions'
 import { useAppDispatch, useAppSelector } from '../../../hooks'
 import { CompetitionFilterParams } from '../../../interfaces/FilterParams'
@@ -43,6 +43,7 @@ const CompetitionManager: React.FC = (props: any) => {
   const classes = useStyles()
   const noFilterText = 'Alla'
   const dispatch = useAppDispatch()
+  const history = useHistory()
   const handleClick = (event: React.MouseEvent<HTMLButtonElement>, id: number) => {
     setAnchorEl(event.currentTarget)
     setActiveId(id)
@@ -174,7 +175,7 @@ const CompetitionManager: React.FC = (props: any) => {
         onChangePage={(event, newPage) => handleFilterChange({ ...filterParams, page: newPage })}
       />
       <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
-        <MenuItem onClick={handleClose}>Starta</MenuItem>
+        <MenuItem onClick={() => history.push(`/presenter/id=${activeId}&code=123123`)}>Starta</MenuItem>
         <MenuItem onClick={handleClose}>Duplicera</MenuItem>
         <RemoveMenuItem onClick={handleDeleteCompetition}>Ta bort</RemoveMenuItem>
       </Menu>
diff --git a/client/src/pages/views/AudienceViewPage.test.tsx b/client/src/pages/views/AudienceViewPage.test.tsx
index 1573f3a4..d00d4277 100644
--- a/client/src/pages/views/AudienceViewPage.test.tsx
+++ b/client/src/pages/views/AudienceViewPage.test.tsx
@@ -1,7 +1,13 @@
 import { render } from '@testing-library/react'
 import React from 'react'
+import { Provider } from 'react-redux'
+import store from '../../store'
 import AudienceViewPage from './AudienceViewPage'
 
 it('renders audience view page', () => {
-  render(<AudienceViewPage />)
+  render(
+    <Provider store={store}>
+      <AudienceViewPage />
+    </Provider>
+  )
 })
diff --git a/client/src/pages/views/JudgeViewPage.test.tsx b/client/src/pages/views/JudgeViewPage.test.tsx
index 5ff1cc5d..537dae4c 100644
--- a/client/src/pages/views/JudgeViewPage.test.tsx
+++ b/client/src/pages/views/JudgeViewPage.test.tsx
@@ -1,7 +1,43 @@
 import { render } from '@testing-library/react'
+import mockedAxios from 'axios'
 import React from 'react'
+import { Provider } from 'react-redux'
+import { BrowserRouter } from 'react-router-dom'
+import store from '../../store'
 import JudgeViewPage from './JudgeViewPage'
 
 it('renders judge view page', () => {
-  render(<JudgeViewPage />)
+  const compRes: any = {
+    data: {
+      slides: [{ id: 0, title: '' }],
+    },
+  }
+  const teamsRes: any = {
+    data: {
+      items: [
+        {
+          id: 1,
+          name: 'team1',
+        },
+        {
+          id: 2,
+          name: 'team2',
+        },
+      ],
+      count: 2,
+      total_count: 3,
+    },
+  }
+
+  ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => {
+    if (path.endsWith('/teams')) return Promise.resolve(teamsRes)
+    else return Promise.resolve(compRes)
+  })
+  render(
+    <BrowserRouter>
+      <Provider store={store}>
+        <JudgeViewPage />
+      </Provider>
+    </BrowserRouter>
+  )
 })
diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx
index 457ebe08..293a1f08 100644
--- a/client/src/pages/views/JudgeViewPage.tsx
+++ b/client/src/pages/views/JudgeViewPage.tsx
@@ -1,7 +1,109 @@
-import React from 'react'
+import { Divider, List, ListItemText } from '@material-ui/core'
+import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
+import React, { useEffect, useState } from 'react'
+import { useParams } from 'react-router-dom'
+import { getPresentationCompetition, getPresentationTeams, setCurrentSlide } from '../../actions/presentation'
+import { useAppDispatch, useAppSelector } from '../../hooks'
+import { ViewParams } from '../../interfaces/ViewParams'
+import { SlideListItem } from '../presentationEditor/styled'
+import JudgeScoreDisplay from './components/JudgeScoreDisplay'
+import SlideDisplay from './components/SlideDisplay'
+import {
+  Content,
+  JudgeAnswersLabel,
+  JudgeAppBar,
+  JudgeQuestionsLabel,
+  JudgeToolbar,
+  LeftDrawer,
+  RightDrawer,
+} from './styled'
+
+const leftDrawerWidth = 150
+const rightDrawerWidth = 390
+
+const useStyles = makeStyles((theme: Theme) =>
+  createStyles({
+    leftDrawerPaper: {
+      width: leftDrawerWidth,
+    },
+    rightDrawerPaper: {
+      width: rightDrawerWidth,
+    },
+    toolbar: theme.mixins.toolbar,
+  })
+)
 
 const JudgeViewPage: React.FC = () => {
-  return <div>Judge</div>
+  const classes = useStyles()
+  const { id, code }: ViewParams = useParams()
+  const dispatch = useAppDispatch()
+  const [activeSlideIndex, setActiveSlideIndex] = useState<number>(0)
+  useEffect(() => {
+    dispatch(getPresentationCompetition(id))
+    dispatch(getPresentationTeams(id))
+  }, [])
+  const teams = useAppSelector((state) => state.presentation.teams)
+  const slides = useAppSelector((state) => state.presentation.competition.slides)
+  const handleSelectSlide = (index: number) => {
+    setActiveSlideIndex(index)
+    dispatch(setCurrentSlide(slides[index]))
+  }
+  return (
+    <div>
+      <JudgeAppBar position="fixed">
+        <JudgeToolbar>
+          <JudgeQuestionsLabel variant="h5">Frågor</JudgeQuestionsLabel>
+          <JudgeAnswersLabel variant="h5">Svar</JudgeAnswersLabel>
+        </JudgeToolbar>
+      </JudgeAppBar>
+      <LeftDrawer
+        width={leftDrawerWidth}
+        variant="permanent"
+        classes={{
+          paper: classes.leftDrawerPaper,
+        }}
+        anchor="left"
+      >
+        <div className={classes.toolbar} />
+        <List>
+          {slides.map((slide, index) => (
+            <SlideListItem
+              selected={index === activeSlideIndex}
+              onClick={() => handleSelectSlide(index)}
+              divider
+              button
+              key={slide.id}
+            >
+              <ListItemText primary={slide.title} />
+            </SlideListItem>
+          ))}
+        </List>
+      </LeftDrawer>
+      <RightDrawer
+        width={rightDrawerWidth}
+        variant="permanent"
+        classes={{
+          paper: classes.rightDrawerPaper,
+        }}
+        anchor="right"
+      >
+        <div className={classes.toolbar} />
+        <List>
+          {teams.map((answer, index) => (
+            <div key={answer.name}>
+              <JudgeScoreDisplay teamIndex={index} />
+              <Divider />
+            </div>
+          ))}
+        </List>
+      </RightDrawer>
+      aaa
+      <Content leftDrawerWidth={leftDrawerWidth} rightDrawerWidth={rightDrawerWidth}>
+        <div className={classes.toolbar} />
+        <SlideDisplay />
+      </Content>
+    </div>
+  )
 }
 
 export default JudgeViewPage
diff --git a/client/src/pages/views/ParticipantViewPage.test.tsx b/client/src/pages/views/ParticipantViewPage.test.tsx
index f7154a9d..85360e4f 100644
--- a/client/src/pages/views/ParticipantViewPage.test.tsx
+++ b/client/src/pages/views/ParticipantViewPage.test.tsx
@@ -1,7 +1,13 @@
 import { render } from '@testing-library/react'
 import React from 'react'
+import { Provider } from 'react-redux'
+import store from '../../store'
 import ParticipantViewPage from './ParticipantViewPage'
 
 it('renders participant view page', () => {
-  render(<ParticipantViewPage />)
+  render(
+    <Provider store={store}>
+      <ParticipantViewPage />
+    </Provider>
+  )
 })
diff --git a/client/src/pages/views/ParticipantViewPage.tsx b/client/src/pages/views/ParticipantViewPage.tsx
index a5ff1f4f..55c28af0 100644
--- a/client/src/pages/views/ParticipantViewPage.tsx
+++ b/client/src/pages/views/ParticipantViewPage.tsx
@@ -1,7 +1,8 @@
 import React from 'react'
+import SlideDisplay from './components/SlideDisplay'
 
 const ParticipantViewPage: React.FC = () => {
-  return <div>Deltagare</div>
+  return <SlideDisplay />
 }
 
 export default ParticipantViewPage
diff --git a/client/src/pages/views/PresenterViewPage.test.tsx b/client/src/pages/views/PresenterViewPage.test.tsx
new file mode 100644
index 00000000..fd7b0a96
--- /dev/null
+++ b/client/src/pages/views/PresenterViewPage.test.tsx
@@ -0,0 +1,43 @@
+import { render } from '@testing-library/react'
+import mockedAxios from 'axios'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { BrowserRouter } from 'react-router-dom'
+import store from '../../store'
+import PresenterViewPage from './PresenterViewPage'
+
+it('renders presenter view page', () => {
+  const compRes: any = {
+    data: {
+      slides: [{ id: 0, title: '' }],
+    },
+  }
+  const teamsRes: any = {
+    data: {
+      items: [
+        {
+          id: 1,
+          name: 'team1',
+        },
+        {
+          id: 2,
+          name: 'team2',
+        },
+      ],
+      count: 2,
+      total_count: 3,
+    },
+  }
+
+  ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => {
+    if (path.endsWith('/teams')) return Promise.resolve(teamsRes)
+    else return Promise.resolve(compRes)
+  })
+  render(
+    <BrowserRouter>
+      <Provider store={store}>
+        <PresenterViewPage />
+      </Provider>
+    </BrowserRouter>
+  )
+})
diff --git a/client/src/pages/views/PresenterViewPage.tsx b/client/src/pages/views/PresenterViewPage.tsx
new file mode 100644
index 00000000..131bde22
--- /dev/null
+++ b/client/src/pages/views/PresenterViewPage.tsx
@@ -0,0 +1,74 @@
+import { List, ListItem, Popover } from '@material-ui/core'
+import ChevronRightIcon from '@material-ui/icons/ChevronRight'
+import React, { useEffect } from 'react'
+import { useHistory, useParams } from 'react-router-dom'
+import {
+  getPresentationCompetition,
+  getPresentationTeams,
+  setCurrentSlideNext,
+  setCurrentSlidePrevious,
+} from '../../actions/presentation'
+import { useAppDispatch, useAppSelector } from '../../hooks'
+import { ViewParams } from '../../interfaces/ViewParams'
+import SlideDisplay from './components/SlideDisplay'
+import { PresenterButton, PresenterContainer, PresenterFooter, PresenterHeader } from './styled'
+
+const PresenterViewPage: React.FC = () => {
+  const teams = useAppSelector((state) => state.presentation.teams)
+  const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null)
+  const { id, code }: ViewParams = useParams()
+  const history = useHistory()
+  const dispatch = useAppDispatch()
+  useEffect(() => {
+    dispatch(getPresentationCompetition(id))
+    dispatch(getPresentationTeams(id))
+  }, [])
+  const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>) => {
+    setAnchorEl(event.currentTarget)
+  }
+  const handleClose = () => {
+    setAnchorEl(null)
+  }
+  return (
+    <PresenterContainer>
+      <PresenterHeader>
+        <PresenterButton onClick={handleOpenPopover} color="primary" variant="contained">
+          Visa ställning
+        </PresenterButton>
+        <PresenterButton onClick={() => history.push('/admin')} variant="contained" color="secondary">
+          Avsluta tävling
+        </PresenterButton>
+      </PresenterHeader>
+      <SlideDisplay />
+      <PresenterFooter>
+        <PresenterButton onClick={() => dispatch(setCurrentSlidePrevious())} variant="contained">
+          <ChevronRightIcon fontSize="large" />
+        </PresenterButton>
+        <PresenterButton onClick={() => dispatch(setCurrentSlideNext())} variant="contained">
+          <ChevronRightIcon fontSize="large" />
+        </PresenterButton>
+      </PresenterFooter>
+      <Popover
+        open={Boolean(anchorEl)}
+        anchorEl={anchorEl}
+        onClose={handleClose}
+        anchorOrigin={{
+          vertical: 'bottom',
+          horizontal: 'center',
+        }}
+        transformOrigin={{
+          vertical: 'top',
+          horizontal: 'center',
+        }}
+      >
+        <List>
+          {teams.map((team) => (
+            <ListItem key={team.id}>{team.name} score: 20</ListItem>
+          ))}
+        </List>
+      </Popover>
+    </PresenterContainer>
+  )
+}
+
+export default PresenterViewPage
diff --git a/client/src/pages/views/components/JudgeScoreDisplay.tsx b/client/src/pages/views/components/JudgeScoreDisplay.tsx
new file mode 100644
index 00000000..ae4d8ab3
--- /dev/null
+++ b/client/src/pages/views/components/JudgeScoreDisplay.tsx
@@ -0,0 +1,39 @@
+import { Box, Typography } from '@material-ui/core'
+import React from 'react'
+import { useAppSelector } from '../../../hooks'
+import { AnswerContainer, ScoreDisplayContainer, ScoreDisplayHeader, ScoreInput } from './styled'
+
+type ScoreDisplayProps = {
+  teamIndex: number
+}
+const questionMaxScore = 5
+
+const JudgeScoreDisplay = ({ teamIndex }: ScoreDisplayProps) => {
+  const currentTeam = useAppSelector((state) => state.presentation.teams[teamIndex])
+  return (
+    <ScoreDisplayContainer>
+      <ScoreDisplayHeader>
+        <Typography variant="h5">
+          <Box fontWeight="fontWeightBold">{currentTeam.name}</Box>
+        </Typography>
+
+        <ScoreInput
+          label="Poäng"
+          defaultValue={0}
+          inputProps={{ style: { fontSize: 20 } }}
+          InputProps={{ disableUnderline: true, inputProps: { min: 0, max: questionMaxScore } }}
+          type="number"
+        ></ScoreInput>
+      </ScoreDisplayHeader>
+      <Typography variant="h6">Alla poäng: 2 0 0 0 0 0 0 0 0</Typography>
+      <Typography variant="h6">Total poäng: 9</Typography>
+      <AnswerContainer>
+        <Typography variant="body1">
+          Svar: blablablablablablablablablabla blablablablabla blablablablabla blablablablablablablablablabla{' '}
+        </Typography>
+      </AnswerContainer>
+    </ScoreDisplayContainer>
+  )
+}
+
+export default JudgeScoreDisplay
diff --git a/client/src/pages/views/components/SlideDisplay.test.tsx b/client/src/pages/views/components/SlideDisplay.test.tsx
new file mode 100644
index 00000000..1a661d33
--- /dev/null
+++ b/client/src/pages/views/components/SlideDisplay.test.tsx
@@ -0,0 +1,13 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import store from '../../../store'
+import SlideDisplay from './SlideDisplay'
+
+it('renders slide display', () => {
+  render(
+    <Provider store={store}>
+      <SlideDisplay />
+    </Provider>
+  )
+})
diff --git a/client/src/pages/views/components/SlideDisplay.tsx b/client/src/pages/views/components/SlideDisplay.tsx
new file mode 100644
index 00000000..1c10f9cf
--- /dev/null
+++ b/client/src/pages/views/components/SlideDisplay.tsx
@@ -0,0 +1,15 @@
+import { Typography } from '@material-ui/core'
+import React from 'react'
+import { useAppSelector } from '../../../hooks'
+import { SlideContainer } from './styled'
+
+const SlideDisplay: React.FC = () => {
+  const currentSlide = useAppSelector((state) => state.presentation.slide)
+  return (
+    <SlideContainer>
+      <Typography variant="h3">{currentSlide.title}</Typography>
+    </SlideContainer>
+  )
+}
+
+export default SlideDisplay
diff --git a/client/src/pages/views/components/styled.tsx b/client/src/pages/views/components/styled.tsx
new file mode 100644
index 00000000..0034c39b
--- /dev/null
+++ b/client/src/pages/views/components/styled.tsx
@@ -0,0 +1,27 @@
+import { TextField } from '@material-ui/core'
+import styled from 'styled-components'
+
+export const SlideContainer = styled.div`
+  display: flex;
+  justify-content: center;
+`
+
+export const ScoreDisplayContainer = styled.div`
+  padding-top: 5px;
+  padding-right: 10px;
+  padding-left: 10px;
+`
+
+export const ScoreDisplayHeader = styled.div`
+  display: flex;
+  justify-content: space-between;
+`
+
+export const ScoreInput = styled(TextField)`
+  width: 40px;
+`
+
+export const AnswerContainer = styled.div`
+  display: flex;
+  flex-wrap: wrap;
+`
diff --git a/client/src/pages/views/styled.tsx b/client/src/pages/views/styled.tsx
index 0dfbf495..261727b9 100644
--- a/client/src/pages/views/styled.tsx
+++ b/client/src/pages/views/styled.tsx
@@ -1,5 +1,23 @@
+import { AppBar, Button, Drawer, Toolbar, Typography } from '@material-ui/core'
 import styled from 'styled-components'
 
+export const JudgeAppBar = styled(AppBar)`
+  z-index: 9000;
+`
+
+export const JudgeToolbar = styled(Toolbar)`
+  display: flex;
+  justify-content: space-between;
+`
+
+export const JudgeQuestionsLabel = styled(Typography)`
+  margin-left: 15px;
+`
+
+export const JudgeAnswersLabel = styled(Typography)`
+  margin-right: 160px;
+`
+
 export const ViewSelectContainer = styled.div`
   display: flex;
   justify-content: center;
@@ -15,4 +33,58 @@ export const ViewSelectButtonGroup = styled.div`
   height: 140px;
   margin-left: auto;
   margin-right: auto;
-`
\ No newline at end of file
+`
+
+export const PresenterHeader = styled.div`
+  display: flex;
+  justify-content: space-between;
+  position: fixed;
+  width: 100%;
+`
+
+export const PresenterFooter = styled.div`
+  display: flex;
+  justify-content: space-between;
+`
+
+export const PresenterButton = styled(Button)`
+  width: 100px;
+  height: 100px;
+  padding-top: 16px;
+  padding-bottom: 16px;
+`
+
+export const PresenterContainer = styled.div`
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  height: 100%;
+`
+
+interface DrawerProps {
+  width: number
+}
+
+export const LeftDrawer = styled(Drawer)<DrawerProps>`
+  flex-shrink: 0;
+  position: 'relative';
+  z-index: -5;
+  width: ${(props) => (props ? props.width : 150)};
+`
+
+export const RightDrawer = styled(Drawer)<DrawerProps>`
+  width: ${(props) => (props ? props.width : 150)};
+  flex-shrink: 0;
+  z-index: 1;
+`
+
+interface ContentProps {
+  leftDrawerWidth: number
+  rightDrawerWidth: number
+}
+
+export const Content = styled.div<ContentProps>`
+  margin-left: ${(props) => (props ? props.leftDrawerWidth : 0)}px;
+  margin-right: ${(props) => (props ? props.rightDrawerWidth : 0)}px;
+  width: calc(100% - ${(props) => (props ? props.leftDrawerWidth + props.rightDrawerWidth : 0)}px);
+`
diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts
index 3c9f2b6c..94743ff1 100644
--- a/client/src/reducers/allReducers.ts
+++ b/client/src/reducers/allReducers.ts
@@ -3,6 +3,7 @@
 import { combineReducers } from 'redux'
 import citiesReducer from './citiesReducer'
 import competitionsReducer from './competitionsReducer'
+import presentationReducer from './presentationReducer'
 import rolesReducer from './rolesReducer'
 import searchUserReducer from './searchUserReducer'
 import uiReducer from './uiReducer'
@@ -14,6 +15,7 @@ const allReducers = combineReducers({
   UI: uiReducer,
   competitions: competitionsReducer,
   cities: citiesReducer,
+  presentation: presentationReducer,
   roles: rolesReducer,
   searchUsers: searchUserReducer,
 })
diff --git a/client/src/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts
new file mode 100644
index 00000000..d71bcafb
--- /dev/null
+++ b/client/src/reducers/presentationReducer.ts
@@ -0,0 +1,72 @@
+import { AnyAction } from 'redux'
+import Types from '../actions/types'
+import { RichCompetition } from './../interfaces/ApiRichModels'
+import { Slide } from './../interfaces/Slide'
+import { Team } from './../interfaces/Team'
+
+interface PresentationState {
+  competition: RichCompetition
+  slide: Slide
+  teams: Team[]
+}
+
+const initialState: PresentationState = {
+  competition: {
+    name: '',
+    id: 0,
+    city: {
+      id: 0,
+      name: '',
+    },
+    slides: [],
+    year: 0,
+    teams: [],
+  },
+  slide: {
+    competition_id: 0,
+    id: 0,
+    order: 0,
+    timer: 0,
+    title: '',
+  },
+  teams: [],
+}
+
+export default function (state = initialState, action: AnyAction) {
+  switch (action.type) {
+    case Types.SET_PRESENTATION_COMPETITION:
+      return {
+        ...state,
+        slide: action.payload.slides[0] as Slide,
+        competition: action.payload as RichCompetition,
+      }
+    case Types.SET_PRESENTATION_TEAMS:
+      return {
+        ...state,
+        teams: action.payload as Team[],
+      }
+    case Types.SET_PRESENTATION_SLIDE:
+      return {
+        ...state,
+        slide: action.payload as Slide,
+      }
+    case Types.SET_PRESENTATION_SLIDE_PREVIOUS:
+      if (state.slide.order - 1 >= 0) {
+        return {
+          ...state,
+          slide: state.competition.slides[state.slide.order - 1],
+        }
+      }
+      return state
+    case Types.SET_PRESENTATION_SLIDE_NEXT:
+      if (state.slide.order + 1 < state.competition.slides.length) {
+        return {
+          ...state,
+          slide: state.competition.slides[state.slide.order + 1],
+        }
+      }
+      return state
+    default:
+      return state
+  }
+}
diff --git a/client/src/styled.tsx b/client/src/styled.tsx
index 0c17f9cc..58cf0c24 100644
--- a/client/src/styled.tsx
+++ b/client/src/styled.tsx
@@ -1,6 +1,5 @@
 import styled from 'styled-components'
 
 export const Wrapper = styled.div`
-  padding: 10px;
   height: 100%;
 `
-- 
GitLab