diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 0000000000000000000000000000000000000000..f8b4888565caadc7510be75682268d6c18edd6de --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 0000000000000000000000000000000000000000..6b6114114f4e89a1f2d0e911ff090e541af1260c --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,6 @@ +{ + "ExpandedNodes": [ + "" + ], + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..2fd9f25a43f737391cedd7aaffa61ebf778acf2a Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/.vs/teknikattan-scoring-system/v16/.suo b/.vs/teknikattan-scoring-system/v16/.suo new file mode 100644 index 0000000000000000000000000000000000000000..39e2dc63c325f04b906b01016e830aa1112dcaab Binary files /dev/null and b/.vs/teknikattan-scoring-system/v16/.suo differ diff --git a/client/.eslintrc b/client/.eslintrc index 7e2cadb0f5dc6cbde1bcb39b5382f7f88f349dde..9234ebf1b6fb8a53273828cac3e34e84285748b1 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 + \ 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/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/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