diff --git a/README.md b/README.md index 7545c4aaf1fce4ddc99bb4cf8baa9ffa71a4bca0..e796ae799246a64cee479f94b614a1c8c9a4c105 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ If there are no more issues for the current or you just found something that nee ### Creating a merge request After solving your issue, you will need to merge your branch into `dev`. -This is in two steps: +This is done in two steps: First you need to prepare your branch to be merged and then create a merge request. First, prepare your branch to be merged. @@ -65,12 +65,10 @@ The next step is to create the actual merge request. 1. On GitLab open `Repository->Branches`. 2. Find your branch and press `Merge request`. -3. Press `Merge Requests` on the left, open your merge request and press the `Mark as ready` button (in the top right corner). +3. Press `Submit merge request` button (in the bottom the left corner). -The test will then run on your changes in the merge request on GitLab. -After the tests have passed and another person has approved your merged request, you will be able to merge. -To merge your branch the tests will have to pass and another person has to approve your merge request. -When this is done, press the `Merge` button. +A green `Merge` button will appear when all tests have passed (run automatically) and another person has approved your merge request. +You cannot approve your own merge requests but once it's approved anyone can merge. ### Merge conflicts @@ -82,7 +80,7 @@ A merge typically looks like the code snippet at the bottom of this document in The only thing you really need to do is removing the `<<<<<<<`, `=======` and `>>>>>>>` symbols from the document, although you don't have to do it by hand. In VSCode, you can simply choose if you want to keep incoming changes (from the branch you merging into), current changes (from your branch) or both. Solve all the merge conflicts in every file and run the tests to make sure it still works. -When you are done, commit and push your changes. +Commit and push your changes when you are done. ``` <<<<<<< file.txt diff --git a/client/.eslintrc b/client/.eslintrc index 7e2cadb0f5dc6cbde1bcb39b5382f7f88f349dde..9f14e433b46b516e8adc6b26ece87084adb3e3bb 100644 --- a/client/.eslintrc +++ b/client/.eslintrc @@ -1,26 +1,27 @@ { - "parser": "@typescript-eslint/parser", - "parserOptions": { - "sourceType": "module", - "project": [ - "tsconfig.json" - ] - }, - "ecmaFeatures": { - "jsx": true - }, - "settings": { - "react": { - "version": "detect" + "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"] } - }, - "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/Redux-readme.txt b/client/Redux-readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..7a20ee65f97e3f5b14a2aed69214c3fe919d3f3c --- /dev/null +++ b/client/Redux-readme.txt @@ -0,0 +1,71 @@ + +This file is a short description of what Redux is, +to learn more about redux, visit https://redux.js.org/ + + +====Install=============================================== +To install Redux type the following in the terminal: + +npm install redux react-redux +npm install @reduxjs/toolkit + +If you have not done so already install axios: + +npm install react-axios +npm install axios + +Also, a good tool to use is the browser plugin. This allows you to se the state in real time. +It even has a useful playback feature. install it here: + +Chrome: +https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd + +Firefox: +https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/ +========================================================= + +What is Redux? + +Redux is a pattern and library for managing and updating +application state, using events called "actions". It serves +as a centralized store for state that needs to be used +across your entire application, with rules ensuring that +the state can only be updated in a predictable fashion. + +The center of every Redux application is the store. +A "store" is a container that holds your application's +global state. + +A store is a JavaScript object with a few special functions +and abilities that make it different than a plain global object: + +- You must never directly modify or change the state that is +kept inside the Redux store + +- Instead, the only way to cause an update to the state is to +create a plain action object that describes "something that +happened in the application", and then dispatch the action to +the store to tell it what happened. + +- When an action is dispatched, the store runs the root reducer +function, and lets it calculate the new state based on the old +state and the action +Finally, the store notifies subscribers that the state has been +updated so the UI can be updated with the new data. + + +Redux uses several types of code: +- Actions are plain objects with a type field, and describe +"what happened" in the app + +- Reducers are functions that calculate a new state value +based on previous state + an action + +- A Redux store runs the root reducer whenever an action +is dispatched + +Cite: https://redux.js.org/tutorials/fundamentals/part-1-overview + + +More useful links on the subject: +https://www.youtube.com/watch?v=CVpUuw9XSjY \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index d6c2bd5b526c987718af784449b39680387b5b9b..ca2a06ebb09f976bec4dfa38b5d7670578d0443a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1895,6 +1895,17 @@ } } }, + "@reduxjs/toolkit": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.5.0.tgz", + "integrity": "sha512-E/FUraRx+8guw9Hlg/Ja8jI/hwCrmIKed8Annt9YsZw3BQp+F24t5I5b2OWR6pkEHY4hn1BgP08FrTZFRKsdaQ==", + "requires": { + "immer": "^8.0.0", + "redux": "^4.0.0", + "redux-thunk": "^2.3.0", + "reselect": "^4.0.0" + } + }, "@rollup/plugin-node-resolve": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", @@ -2312,6 +2323,16 @@ "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==", "dev": true }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", @@ -2414,6 +2435,18 @@ "@types/react": "*" } }, + "@types/react-redux": { + "version": "7.1.16", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz", + "integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "@types/react-router": { "version": "5.1.11", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.11.tgz", @@ -7844,9 +7877,9 @@ "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" }, "immer": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.9.tgz", - "integrity": "sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A==" + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz", + "integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==" }, "import-cwd": { "version": "2.1.0", @@ -11033,9 +11066,9 @@ } }, "open": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.0.tgz", - "integrity": "sha512-PGoBCX/lclIWlpS/R2PQuIR4NJoXh6X5AwVzE7WXnWRGvHg7+4TBCgsujUgiPpm0K1y4qvQeWnCWVTpTKZBtvA==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "requires": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -12771,10 +12804,15 @@ "whatwg-fetch": "^3.4.1" } }, + "react-axios": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-axios/-/react-axios-2.0.4.tgz", + "integrity": "sha512-QsTq7C/NwsjfrSmFVxPo29BdX6DtLpRF0fZTJv5/R4BanOm+c4639B3Xb4lF83ZfAOX5IW8XG7htz4V+WNF+WA==" + }, "react-dev-utils": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.2.tgz", - "integrity": "sha512-xG7GlMoYkrgc2M1kDCHKRywXMDbFnjOB+/VzpytQyYBusEzR8NlGTMmUbvN86k94yyKu5XReHB8eZC2JZrNchQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.3.tgz", + "integrity": "sha512-4lEA5gF4OHrcJLMUV1t+4XbNDiJbsAWCH5Z2uqlTqW6dD7Cf5nEASkeXrCI/Mz83sI2o527oBIFKVMXtRf1Vtg==", "requires": { "@babel/code-frame": "7.10.4", "address": "1.1.2", @@ -12789,7 +12827,7 @@ "global-modules": "2.0.0", "globby": "11.0.1", "gzip-size": "5.1.1", - "immer": "7.0.9", + "immer": "8.0.1", "is-root": "2.1.0", "loader-utils": "2.0.0", "open": "^7.0.2", @@ -12902,6 +12940,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-redux": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.2.tgz", + "integrity": "sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==", + "requires": { + "@babel/runtime": "^7.12.1", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^16.13.1" + } + }, "react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -13223,6 +13273,25 @@ "strip-indent": "^3.0.0" } }, + "redux": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", + "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", + "requires": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + } + }, + "redux-devtools-extension": { + "version": "2.13.8", + "resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz", + "integrity": "sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg==" + }, + "redux-thunk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", + "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==" + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -13449,6 +13518,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz", + "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==" + }, "resolve": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", diff --git a/client/package.json b/client/package.json index 4d2df9fcb423cf855346e2cfe6a3ca81057520c7..58910bbfdb86bf5aea1a21548fc9e0d1d5bb9acb 100644 --- a/client/package.json +++ b/client/package.json @@ -6,6 +6,7 @@ "@material-ui/core": "^4.11.3", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.57", + "@reduxjs/toolkit": "^1.5.0", "@testing-library/jest-dom": "^5.11.9", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^12.6.3", @@ -16,14 +17,19 @@ "axios": "^0.21.1", "formik": "^2.2.6", "react": "^17.0.1", + "react-axios": "^2.0.4", "react-dom": "^17.0.1", + "react-redux": "^7.2.2", "react-router-dom": "^5.2.0", "react-scripts": "4.0.2", + "redux": "^4.0.5", + "redux-devtools-extension": "^2.13.8", "typescript": "^4.1.3", "web-vitals": "^1.1.0", "yup": "^0.32.9" }, "devDependencies": { + "@types/react-redux": "^7.1.16", "@types/react-router-dom": "^5.1.7", "@typescript-eslint/eslint-plugin": "4.2.0", "@typescript-eslint/parser": "4.2.0", diff --git a/client/src/App.tsx b/client/src/App.tsx index 60e4ee38d107148d84f03bc76821e7fd0138099d..c80013fd85ff12b484ae7abd705fab9b1befe608 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' @@ -8,6 +9,9 @@ const theme = createMuiTheme({ primary: { main: '#6200EE', }, + secondary: { + main: teal.A400, + }, }, }) diff --git a/client/src/Main.tsx b/client/src/Main.tsx index 5ad533a6203b5dc1399fa89381bb13d3a35180a6..568d101035f8b71b088af01616c4e8ea8101140a 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/actions/Action_Explanation.txt b/client/src/actions/Action_Explanation.txt new file mode 100644 index 0000000000000000000000000000000000000000..dec743a115aa5e3b181fceb47a2241c7443e1233 --- /dev/null +++ b/client/src/actions/Action_Explanation.txt @@ -0,0 +1,8 @@ +An action is a plain JavaScript object that has a type field. +The actions, the events that occur in the app based on user input, +and trigger updates in the state + +You can think of an action as an event that describes something +that happened in the application. + +https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow \ No newline at end of file diff --git a/client/src/actions/communication.js b/client/src/actions/communication.js new file mode 100644 index 0000000000000000000000000000000000000000..587f431ea260e09400d948fe1bab73d08b60a784 --- /dev/null +++ b/client/src/actions/communication.js @@ -0,0 +1,62 @@ +import Types from "./types.js"; + +export function axiosPost(path, data, config = undefined, + startCB = undefined, successCB = undefined, errorCB = undefined){ + return { + type: Types.AXIOS_POST, + path, + data, + config, + startCB, + successCB, + errorCB + } +} + +export function axiosPostSuccess(path, data, previousAction){ + return { + type: Types.AXIOS_POST_SUCCESS, + path, + data, + previousAction + } +} + +export function axiosPostError(path, data, previousAction){ + return { + type: Types.AXIOS_POST_ERROR, + path, + data, + previousAction + } +} + +export function axiosGet(path, data, config = undefined, startCB = undefined, successCB = undefined, errorCB = undefined) { + return { + type: Types.AXIOS_GET, + path, + data, + config, + startCB, + successCB, + errorCB + } +} + +export function axiosGetSuccess(path, data, previousAction){ + return { + type: Types.AXIOS_GET_SUCCESS, + path, + data, + previousAction + } +} + +export function axiosGetError(path, data, previousAction){ + return { + type: Types.AXIOS_GET_ERROR, + path, + data, + previousAction + } +} diff --git a/client/src/actions/login.js b/client/src/actions/login.js new file mode 100644 index 0000000000000000000000000000000000000000..3ad925a18f8c5853532291a71d5d1e4d0511033c --- /dev/null +++ b/client/src/actions/login.js @@ -0,0 +1,29 @@ + +export const login = () => { + return{ + type: 'SIGN_IN' + }; +}; + + + + +/* +// Old code that can be used for comparison + +export function login(name, email, id, token) { + return { + type: Types.USER_LOGIN, + name, + email, + id, + token + } +} + +export function logout() { + return { + type: Types.USER_LOGOUT, + } +} +*/ \ No newline at end of file diff --git a/client/src/actions/types.js b/client/src/actions/types.js new file mode 100644 index 0000000000000000000000000000000000000000..3d640d40050f5c4a4b3418c8daa679f9e4096ef9 --- /dev/null +++ b/client/src/actions/types.js @@ -0,0 +1,11 @@ +export default { + AXIOS_GET: "AXIOS_GET", + AXIOS_GET_SUCCESS: "AXIOS_GET_SUCCESS", + AXIOS_GET_ERROR: "AXIOS_GET_ERROR", + + AXIOS_POST: "AXIOS_POST", + AXIOS_POST_SUCCESS: "AXIOS_POST_SUCCESS", + AXIOS_POST_ERROR: "AXIOS_POST_ERROR", + + +} diff --git a/client/src/index.tsx b/client/src/index.tsx index e7b11a230f0a06760e23141dc90d09baee11479a..e60483219a7187cad22aaac7fa4fcd30b1db727a 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -1,13 +1,40 @@ import React from 'react' import ReactDOM from 'react-dom' +import { Provider } from 'react-redux' +import { compose, createStore } from 'redux' import App from './App' import './index.css' +import allReducers from './reducers/allReducers' import reportWebVitals from './reportWebVitals' +/* + TypeScript does not know the type of the property. + Therefore, you will get the error; Property ‘__REDUX_DEVTOOLS_EXTENSION_COMPOSE__’ + does not exist on type ‘Window’. Hence, you need to add the property to the global window as below. +*/ +declare global { + interface Window { + __REDUX_DEVTOOLS_EXTENSION__: typeof compose + } +} + +// Create an Advanced global store with the name "store" +// const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose // allows Mozilla plugin to view state in a GUI, https://github.com/zalmoxisus/redux-devtools-extension#13-use-redux-devtools-extension-package-from-npm +// const store = createStore(allReducers, composeEnhancers(applyMiddleware())) + +// simple store with plugin +const store = createStore( + allReducers, + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() +) + +// Provider wraps the app component so that it can access store ReactDOM.render( - <React.StrictMode> - <App /> - </React.StrictMode>, + <Provider store={store}> + <React.StrictMode> + <App /> + </React.StrictMode> + </Provider>, document.getElementById('root') ) diff --git a/client/src/middleware/Middleware_Explanation.txt b/client/src/middleware/Middleware_Explanation.txt new file mode 100644 index 0000000000000000000000000000000000000000..dc4e91ab6fd33627a7b19cb06be5ba20fee4598d --- /dev/null +++ b/client/src/middleware/Middleware_Explanation.txt @@ -0,0 +1,6 @@ +Redux middleware provides a third-party extension point between dispatching an action, +and the moment it reaches the reducer. People use Redux middleware for logging, +crash reporting, talking to an asynchronous API, routing, and more. + + +https://redux.js.org/tutorials/fundamentals/part-4-store \ 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 b2a717e30fe5b0595f6ebb3143387988a9476d7b..a85378ab083930051dd595db5c74b411f49d6bb3 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 0000000000000000000000000000000000000000..2d3d142b16b81330816316bb5993360e286abbf2 --- /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 e5f1366ef74167317e53f52b8ed9b804ce6531f0..c48e89582f00bb5906e09bf16fe557d7be54282d 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 0000000000000000000000000000000000000000..724786349bf514c3bee7c2f48ec3ca64adec0b96 --- /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 0000000000000000000000000000000000000000..946befe71e75a33d80eafe0067a4238f641059f1 --- /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 diff --git a/client/src/reducers/Reducer_Explanation.txt b/client/src/reducers/Reducer_Explanation.txt new file mode 100644 index 0000000000000000000000000000000000000000..dea1357be0735bd1f042910d8d53de59bf52ed52 --- /dev/null +++ b/client/src/reducers/Reducer_Explanation.txt @@ -0,0 +1,7 @@ +A reducer is a function that receives the current state and an action object, +decides how to update the state if necessary, and returns the new state: +(state, action) => newState. +You can think of a reducer as an event listener which handles events based +on the received action (event) type. + +https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow \ No newline at end of file diff --git a/client/src/reducers/allReducers.js b/client/src/reducers/allReducers.js new file mode 100644 index 0000000000000000000000000000000000000000..2bfcd5ead021f503794ff14e7b7f3a9da0473129 --- /dev/null +++ b/client/src/reducers/allReducers.js @@ -0,0 +1,11 @@ +// Combines all the reducers so that we only have to pass "one" reducer to the store in src/index.tsx + + +import { combineReducers } from 'redux'; +import loggedInReducer from './isLoggedIn'; + +const allReducers = combineReducers({ + // name: state + isLoggedIn: loggedInReducer // You can write "loggedInReducer" because its the same as "loggedInReducer: loggedInReducer" +}); +export default allReducers; \ No newline at end of file diff --git a/client/src/reducers/isLoggedIn.js b/client/src/reducers/isLoggedIn.js new file mode 100644 index 0000000000000000000000000000000000000000..b665894a5d8136391d34e8512c631867d2a851f2 --- /dev/null +++ b/client/src/reducers/isLoggedIn.js @@ -0,0 +1,9 @@ +const loggedInReducer = (state = false, action) => { // isLoggedIn has an initial state of false + switch (action.type) { + case 'SIGN_IN': + return !state; + default: + return state; + } +} +export default loggedInReducer