diff --git a/wtwui/.gitignore b/wtwui/.gitignore index 529310e3b8c2ab94933cc523161962dc38f048fc..3d033a9c5425ade1c437eeb8373a2c3db8f25944 100644 --- a/wtwui/.gitignore +++ b/wtwui/.gitignore @@ -2,6 +2,7 @@ # dependencies /node_modules +/.idea /.pnp .pnp.js .package-lock.json diff --git a/wtwui/package-lock.json b/wtwui/package-lock.json index aeb5d4bc5a2fc05b8a71d4ae252b2366ef0edfd5..675e5fc1f1e22802fd00333aac704239d980c02f 100644 --- a/wtwui/package-lock.json +++ b/wtwui/package-lock.json @@ -2259,6 +2259,15 @@ "@types/node": "*" } }, + "@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==", + "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", @@ -2354,6 +2363,17 @@ "csstype": "^3.0.2" } }, + "@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==", + "requires": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "@types/react-transition-group": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz", @@ -3082,6 +3102,14 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.4.tgz", "integrity": "sha512-Pdgfv6iP0gNx9ejRGa3zE7Xgkj/iclXqLfe7BnatdZz0QnLZ3jrRHUVH8wNSdN68w05Sk3ShGTb3ydktMTooig==" }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -3638,6 +3666,15 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "optional": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -6541,6 +6578,12 @@ } } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "filesize": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", @@ -7179,6 +7222,19 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -7189,6 +7245,14 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -10082,6 +10146,15 @@ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" }, + "mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "requires": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + } + }, "mini-css-extract-plugin": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", @@ -10273,6 +10346,12 @@ "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "optional": true + }, "nanoid": { "version": "3.1.22", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz", @@ -12392,6 +12471,20 @@ "object-assign": "^4.1.1" } }, + "react-alert": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/react-alert/-/react-alert-7.0.2.tgz", + "integrity": "sha512-oUxPk9DMaEm93Y33mdAmy4vDPZauMj30di4p4+QuZ3JOyoFSFteLSsjlhTkDjkyvJuVxToi3bbnsxehRHEPpeg==", + "requires": { + "prop-types": "^15.7.2", + "react-transition-group": "^4.4.1" + } + }, + "react-alert-template-basic": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/react-alert-template-basic/-/react-alert-template-basic-1.0.0.tgz", + "integrity": "sha512-6x5Us0oc+jj8BDNkvSWfQMESk5SdyGKitXdLb7CwIlIlecyATjCTKSWpLABg8tpKAPOSJu4Dv/fYUqxXEio/XA==" + }, "react-app-polyfill": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz", @@ -12576,11 +12669,70 @@ "warning": "^4.0.3" } }, + "react-redux": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.3.tgz", + "integrity": "sha512-ZhAmQ1lrK+Pyi0ZXNMUZuYxYAZd59wFuVDGUt536kSGdD0ya9Q7BfsE95E3TsFLE3kOSFp5m6G5qbatE+Ic1+w==", + "requires": { + "@babel/runtime": "^7.12.1", + "@types/react-redux": "^7.1.16", + "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", "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==" }, + "react-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "react-router-dom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, "react-scripts": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.3.tgz", @@ -12773,6 +12925,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.9", + "resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz", + "integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==" + }, + "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", @@ -13028,6 +13199,11 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -14399,6 +14575,11 @@ "util.promisify": "~1.0.0" } }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -14681,6 +14862,16 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", @@ -15165,6 +15356,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -15345,7 +15541,11 @@ "version": "1.2.13", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } }, "glob-parent": { "version": "3.1.0", @@ -15944,7 +16144,11 @@ "version": "1.2.13", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } }, "glob-parent": { "version": "3.1.0", diff --git a/wtwui/package.json b/wtwui/package.json index 76722de3f235c79e43767ebf6835efb5ebfe5636..180d35d3224f239d517aab1af618094c94b68eb0 100644 --- a/wtwui/package.json +++ b/wtwui/package.json @@ -6,11 +6,19 @@ "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^11.2.6", "@testing-library/user-event": "^12.8.3", + "axios": "^0.21.1", "bootstrap": "^4.6.0", "react": "^17.0.2", + "react-alert": "^7.0.2", + "react-alert-template-basic": "^1.0.0", "react-bootstrap": "^1.5.2", "react-dom": "^17.0.2", + "react-redux": "^7.2.3", + "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", + "redux": "^4.0.5", + "redux-devtools-extension": "^2.13.9", + "redux-thunk": "^2.3.0", "web-vitals": "^1.1.1" }, "scripts": { diff --git a/wtwui/src/App.js b/wtwui/src/App.js index 7799a750effd3acbbaa945017b361d46540a95d5..3c1ec3ddac85d6aab99039bef0f515e747a5d9bf 100644 --- a/wtwui/src/App.js +++ b/wtwui/src/App.js @@ -1,54 +1,58 @@ +import React, { Component, Fragment } from 'react'; +import ReactDOM from 'react-dom'; +import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; +import { Provider as AlertProvider } from 'react-alert'; +import AlertTemplate from 'react-alert-template-basic'; +import { Provider } from 'react-redux'; +import store from './store'; +import { loadUser } from './actions/auth'; + import "./App.css"; +import "./css/common.css"; import Navbar from "./components/Navbar"; import "bootstrap/dist/css/bootstrap.css"; import Dashboard from "./components/Dashboard"; -import React from 'react'; import '../node_modules/bootstrap/dist/css/bootstrap.min.css'; -import './App.css'; - -import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; - -import Login from "./components/login.component"; -import SignUp from "./components/signup.component"; +import PrivateRoute from "./components/common/PrivateRoute"; +import Alerts from "./components/layout/Alerts"; +import Login from "./components/Login"; +import SignUp from "./components/Signup."; import logo from "./Images/Logo.svg"; -console.log(logo); function Header() { return <img src={logo.svg} alt="Logo" />; } -function App() { - return (<Router> - <div className="App"> - <nav className="navbar navbar-expand-lg navbar-light"> - <div className="container"> - <div className="logo"> - <img src={logo} width="280" height="60"/> - </div> - <div className="collapse navbar-collapse" id="navbarTogglerDemo02"> - <ul className="navbar-nav ml-auto"> - <li className="nav-item"> - <Link className="nav-link" to={"/sign-in"}>Login</Link> - </li> - <li className="nav-item"> - <Link className="nav-link" to={"/sign-up"}>Sign up</Link> - </li> - </ul> - </div> - </div> - </nav> +const alertOptions = { + timeout: 3000, + position: 'top center', +}; - <div className="auth-wrapper"> - <div className="auth-inner"> - <Switch> - <Route exact path='/' component={Login} /> - <Route path="/sign-in" component={Login} /> - <Route path="/sign-up" component={SignUp} /> - </Switch> - </div> - </div> - </div></Router> - ); +class App extends Component { + componentDidMount() { + store.dispatch(loadUser()); + } + render(){ + return( + <Provider store={store}> + <AlertProvider template={AlertTemplate} {...alertOptions}> + <Router> + <Fragment> + <Navbar/> + <Alerts/> + <div className="App"> + <Switch> + <PrivateRoute exact path="/" component={Dashboard} /> + <Route exact path="/register" component={SignUp} /> + <Route exact path="/login" component={Login} /> + </Switch> + </div> + </Fragment> + </Router> + </AlertProvider> + </Provider> + ) + } } export default App; diff --git a/wtwui/src/actions/auth.js b/wtwui/src/actions/auth.js new file mode 100644 index 0000000000000000000000000000000000000000..b27b75416f52da2b635fe8048cf417f4ac5947f0 --- /dev/null +++ b/wtwui/src/actions/auth.js @@ -0,0 +1,89 @@ +import axios from "axios"; +import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGOUT_SUCCESS, LOGIN_SUCCESS, LOGIN_FAIL } from "./types" +import { returnErrors } from './messages'; + +// CHECK TOKEN & LOAD USER + + +export const loadUser = () => (dispatch, getState) => { + // User Loading + dispatch({ type: USER_LOADING }); + axios.all([ + axios.get('http://localhost:8000/api/auth/user', tokenConfig(getState)), + axios.get(`http://localhost:8002/fitnessapi/profile/5/get_profile/`, tokenConfig(getState)) + ]) + .then((res) => { + console.log(res) + dispatch({ + type: USER_LOADED, + payload: res.data, + }); + }) + .catch((err) => { + dispatch(returnErrors(err.response.data, err.response.status)); + dispatch({ + type: AUTH_ERROR, + }); + }); +}; + +// LOGIN USER +export const login = (username, password) => (dispatch) => { + // Headers + const config = { + headers: { + 'Content-Type': 'application/json', + }, + }; + + // Request Body + const body = JSON.stringify({ username, password }); + + axios + .post('http://localhost:8000/api/auth/login', body, config) + .then((res) => { + dispatch({ + type: LOGIN_SUCCESS, + payload: res.data, + }); + }) + .catch((err) => { + dispatch(returnErrors(err.response.data, err.response.status)); + dispatch({ + type: LOGIN_FAIL, + }); + }); +}; + +// LOGOUT USER +export const logout = () => (dispatch, getState) => { + axios + .post('http://localhost:8000/api/auth/logout/', null, tokenConfig(getState)) + .then((res) => { + dispatch({ + type: LOGOUT_SUCCESS, + }); + }) + .catch((err) => { + dispatch(returnErrors(err.response.data, err.response.status)); + }); +}; + +export const tokenConfig = (getState) => { + // Get token from state + const token = getState().auth.token; + + // Headers + const config = { + headers: { + 'Content-Type': 'application/json', + }, + }; + + // If token, add to headers config + if (token) { + config.headers['Authorization'] = `Token ${token}`; + } + + return config; +}; \ No newline at end of file diff --git a/wtwui/src/actions/messages.js b/wtwui/src/actions/messages.js new file mode 100644 index 0000000000000000000000000000000000000000..8f13ebb1c8750565b877cf7aecd697bd8690e58b --- /dev/null +++ b/wtwui/src/actions/messages.js @@ -0,0 +1,17 @@ +import { CREATE_MESSAGE, GET_ERRORS } from './types' + +// CREATE MESSAGE +export const createMessage = (msg) => { + return { + type: CREATE_MESSAGE, + payload: msg, + }; +}; + +// RETURN ERRORS +export const returnErrors = (msg, status) => { + return { + type: GET_ERRORS, + payload: { msg, status }, + }; +}; \ No newline at end of file diff --git a/wtwui/src/actions/types.js b/wtwui/src/actions/types.js new file mode 100644 index 0000000000000000000000000000000000000000..5e74c0ca1151a05026dc39cdc5c051f9bc4e1f0f --- /dev/null +++ b/wtwui/src/actions/types.js @@ -0,0 +1,10 @@ +export const GET_ERRORS = "GET_ERRORS"; +export const USER_LOADING = "USER_LOADING"; +export const CREATE_MESSAGE = "CREATE_MESSAGE"; +export const USER_LOADED = "USER_LOADED"; +export const AUTH_ERROR = "AUTH_ERROR"; +export const LOGIN_SUCCESS = "LOGIN_SUCCESS"; +export const LOGIN_FAIL = "LOGIN_FAIL"; +export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; +export const REGISTER_SUCCESS = 'REGISTER_SUCCESS'; +export const REGISTER_FAIL = 'REGISTER_FAIL'; \ No newline at end of file diff --git a/wtwui/src/components/Login.js b/wtwui/src/components/Login.js new file mode 100644 index 0000000000000000000000000000000000000000..5d6119d252fad4f111e2f726890b8dcf091e62f1 --- /dev/null +++ b/wtwui/src/components/Login.js @@ -0,0 +1,76 @@ +import React, { Component } from 'react'; +import { Link, Redirect } from 'react-router-dom'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { login } from '../actions/auth'; + +export class Login extends Component { + state = { + username: '', + password: '', + }; + static propTypes = { + login: PropTypes.func.isRequired, + isAuthenticated: PropTypes.bool, + }; + onSubmit = (e) => { + e.preventDefault(); + this.props.login(this.state.username, this.state.password); + }; + + onChange = (e) => this.setState({ [e.target.name]: e.target.value }); + + render() { + if (this.props.isAuthenticated) { + return <Redirect to="/" />; + } + const { username, password } = this.state; + return ( + <div className="auth-wrapper"> + <div className="auth-inner"> + <form onSubmit={this.onSubmit}> + <h3>Sign In</h3> + + <div className="form-group"> + <label>Username</label> + <input + type="text" + className="form-control" + name="username" + onChange={this.onChange} + value={username} + /> + </div> + + <div className="form-group"> + <label>Password</label> + <input + type="password" + className="form-control" + name="password" + onChange={this.onChange} + value={password} + /> + </div> + + <div className="form-group"> + <div className="custom-control custom-checkbox"> + <input type="checkbox" className="custom-control-input" id="customCheck1" /> + <label className="custom-control-label" htmlFor="customCheck1">Remember me</label> + </div> + </div> + + <button type="submit" className="btn btn-block" style={{backgroundColor: "#d1b22b", color: "#212529"}}>Submit</button> + </form> + </div> + </div> + ); + } +} + +const mapStateToProps = (state) => ({ + isAuthenticated: state.auth.isAuthenticated, +}); + + +export default connect(mapStateToProps, { login })(Login); \ No newline at end of file diff --git a/wtwui/src/components/Navbar.js b/wtwui/src/components/Navbar.js index be968d253b288cc22a1ba61465a35cbee3abbf53..a1891907a3e2f3c2ec3278a852704bc8173b091c 100644 --- a/wtwui/src/components/Navbar.js +++ b/wtwui/src/components/Navbar.js @@ -1,9 +1,59 @@ -import React, { Component } from "react"; +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { logout } from '../actions/auth'; import logo from "../Images/Logo.svg"; import "../css/Navbar.css"; export class Navbar extends Component { + static propTypes = { + auth: PropTypes.object.isRequired, + logout: PropTypes.func.isRequired, + }; render() { + const { isAuthenticated, user } = this.props.auth; + const authLinks = ( + <ul className="navbar-nav ml-auto mt-2 mt-lg-0"> + <span className="navbar-text mr-3"> + + <strong>{user ? `Welcome ${user.username}` : ''}</strong> + </span> + <li className="nav-item"> + <a onClick={this.props.logout} href="#" className="nav-link"> + Logout + </a> + </li> + </ul> + ) + const authLeftLinks = ( + <ul className="navbar-nav me-auto mb-2 mb-lg-0"> + <li className="nav-item"> + <a className="nav-link active" aria-current="page" href="#"> + Home + </a> + </li> + <li className="nav-item"> + <a className="nav-link" href="#"> + Link + </a> + </li> + </ul> + ) + const guestLinks = ( + <ul className="navbar-nav ml-auto mt-2 mt-lg-0"> + <li className="nav-item"> + <Link to="/register" className="nav-link"> + Register + </Link> + </li> + <li className="nav-item"> + <Link to="/login" className="nav-link"> + Login + </Link> + </li> + </ul> + ); return ( <nav className="navbar navbar-expand-lg navbar-light bg-light"> <div className="container-fluid"> @@ -23,70 +73,22 @@ export class Navbar extends Component { src={logo} alt="Logo" width="300" - height="90" + height="70" textalign="center" ></img> </a> - <div className="collapse navbar-collapse" id="navbarSupportedContent"> - <ul className="navbar-nav me-auto mb-2 mb-lg-0"> - <li className="nav-item"> - <a className="nav-link active" aria-current="page" href="#"> - Home - </a> - </li> - <li className="nav-item"> - <a className="nav-link" href="#"> - Link - </a> - </li> - <li className="nav-item dropdown"> - <a - className="nav-link dropdown-toggle" - href="#" - id="navbarDropdown" - role="button" - data-bs-toggle="dropdown" - aria-expanded="false" - > - Dropdown - </a> - <ul className="dropdown-menu" aria-labelledby="navbarDropdown"> - <li> - <a className="dropdown-item" href="#"> - Action - </a> - </li> - <li> - <a className="dropdown-item" href="#"> - Another action - </a> - </li> - <li> - <hr className="dropdown-divider"></hr> - </li> - <li> - <a className="dropdown-item" href="#"> - Something else here - </a> - </li> - </ul> - </li> - <li className="nav-item"> - <a - className="nav-link disabled" - href="#" - tabIndex="-1" - aria-disabled="true" - > - Disabled - </a> - </li> - </ul> + <div> + {isAuthenticated ? authLeftLinks : ""} </div> + {isAuthenticated ? authLinks : guestLinks} </div> </nav> ); } } -export default Navbar; +const mapStateToProps = (state) => ({ + auth: state.auth, +}); + +export default connect(mapStateToProps, { logout })(Navbar); diff --git a/wtwui/src/components/signup.component.js b/wtwui/src/components/Signup..js similarity index 82% rename from wtwui/src/components/signup.component.js rename to wtwui/src/components/Signup..js index 788521b151d74099eed70e0fbeb3a4c39cae11e9..c5fd94bcaa0fcbf8ef864fb8ace62458d5800b9b 100644 --- a/wtwui/src/components/signup.component.js +++ b/wtwui/src/components/Signup..js @@ -3,6 +3,8 @@ import React, { Component } from "react"; export default class SignUp extends Component { render() { return ( + <div className="auth-wrapper"> + <div className="auth-inner"> <form> <h3>Sign Up</h3> @@ -26,11 +28,13 @@ export default class SignUp extends Component { <input type="password" className="form-control" placeholder="Enter password" /> </div> - <button type="submit" className="btn btn-primary btn-block">Sign Up</button> + <button type="submit" className="btn btn-block" style={{backgroundColor: "#d1b22b", color: "#212529"}}>Sign Up</button> <p className="forgot-password text-right"> Already registered <a href="/sign-in">sign in?</a> </p> </form> + </div> + </div> ); } } \ No newline at end of file diff --git a/wtwui/src/components/common/PrivateRoute.js b/wtwui/src/components/common/PrivateRoute.js new file mode 100644 index 0000000000000000000000000000000000000000..f910f3de1a88eb7ecd2862852f7f3aede82f775e --- /dev/null +++ b/wtwui/src/components/common/PrivateRoute.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { Route, Redirect } from 'react-router-dom'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; + +const PrivateRoute = ({ component: Component, auth, ...rest }) => ( + <Route + {...rest} + render={(props) => { + if (auth.isLoading) { + return <h2>Loading...</h2>; + } else if (!auth.isAuthenticated) { + return <Redirect to="/login" />; + } else { + return <Component {...props} />; + } + }} + /> +); + +const mapStateToProps = (state) => ({ + auth: state.auth, +}); + +export default connect(mapStateToProps)(PrivateRoute); \ No newline at end of file diff --git a/wtwui/src/components/layout/Alerts.js b/wtwui/src/components/layout/Alerts.js new file mode 100644 index 0000000000000000000000000000000000000000..eb2d042d0c361966d3c37ef8595d2893c4350ea5 --- /dev/null +++ b/wtwui/src/components/layout/Alerts.js @@ -0,0 +1,38 @@ +import React, { Component, Fragment } from 'react'; +import { withAlert } from 'react-alert'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +export class Alerts extends Component { + static propTypes = { + error: PropTypes.object.isRequired, + message: PropTypes.object.isRequired, + }; + + componentDidUpdate(prevProps) { + const { error, alert, message } = this.props; + if (error !== prevProps.error) { + if (error.msg.name) alert.error(`Name: ${error.msg.name.join()}`); + if (error.msg.email) alert.error(`Email: ${error.msg.email.join()}`); + if (error.msg.message) alert.error(`Message: ${error.msg.message.join()}`); + if (error.msg.non_field_errors) alert.error(error.msg.non_field_errors.join()); + if (error.msg.username) alert.error(error.msg.username.join()); + } + + if (message !== prevProps.message) { + if (message.deleteLead) alert.success(message.deleteLead); + if (message.addLead) alert.success(message.addLead); + if (message.passwordNotMatch) alert.error(message.passwordNotMatch); + } + } + + render() { + return <Fragment />; + } +} + +const mapStateToProps = (state) => ({ + error: state.errors, + message: state.messages, +}); + +export default connect(mapStateToProps)(withAlert()(Alerts)); \ No newline at end of file diff --git a/wtwui/src/components/login.component.js b/wtwui/src/components/login.component.js deleted file mode 100644 index 019d441ca1d8689ea187c792ea5799a1e6cd7cb5..0000000000000000000000000000000000000000 --- a/wtwui/src/components/login.component.js +++ /dev/null @@ -1,30 +0,0 @@ -import React, { Component } from "react"; - -export default class Login extends Component { - render() { - return ( - <form> - <h3>Sign In</h3> - - <div className="form-group"> - <label>Email address</label> - <input type="email" className="form-control" placeholder="Enter email" /> - </div> - - <div className="form-group"> - <label>Password</label> - <input type="password" className="form-control" placeholder="Enter password" /> - </div> - - <div className="form-group"> - <div className="custom-control custom-checkbox"> - <input type="checkbox" className="custom-control-input" id="customCheck1" /> - <label className="custom-control-label" htmlFor="customCheck1">Remember me</label> - </div> - </div> - - <button type="submit" className="btn btn-primary btn-block">Submit</button> - </form> - ); - } -} \ No newline at end of file diff --git a/wtwui/src/css/common.css b/wtwui/src/css/common.css new file mode 100644 index 0000000000000000000000000000000000000000..6e046634276199b98ac68b7d0d154a132b2aeff6 --- /dev/null +++ b/wtwui/src/css/common.css @@ -0,0 +1,10 @@ +.btn-common { + background-color: #d1b22b; + color: white; + +} + +.form-control:focus { + border-color: #d1b22b; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} \ No newline at end of file diff --git a/wtwui/src/reducers/auth.js b/wtwui/src/reducers/auth.js new file mode 100644 index 0000000000000000000000000000000000000000..c85cf2bc57323177048dfdb33d18465cc4513014 --- /dev/null +++ b/wtwui/src/reducers/auth.js @@ -0,0 +1,58 @@ +import { + USER_LOADED, + USER_LOADING, + AUTH_ERROR, + LOGIN_SUCCESS, + LOGIN_FAIL, + LOGOUT_SUCCESS, + REGISTER_SUCCESS, + REGISTER_FAIL, +} from '../actions/types'; + +const initialState = { + token: localStorage.getItem('token'), + isAuthenticated: null, + isLoading: false, + user: null, +}; + +export default function (state = initialState, action) { + switch (action.type) { + case USER_LOADING: + return { + ...state, + isLoading: true, + }; + case USER_LOADED: + console.log(action.payload) + return { + ...state, + isAuthenticated: true, + isLoading: false, + user: action.payload, + }; + case LOGIN_SUCCESS: + case REGISTER_SUCCESS: + localStorage.setItem('token', action.payload.token); + return { + ...state, + ...action.payload, + isAuthenticated: true, + isLoading: false, + }; + case AUTH_ERROR: + case LOGIN_FAIL: + case LOGOUT_SUCCESS: + case REGISTER_FAIL: + localStorage.removeItem('token'); + return { + ...state, + token: null, + user: null, + isAuthenticated: false, + isLoading: false, + }; + default: + return state; + } +} diff --git a/wtwui/src/reducers/errors.js b/wtwui/src/reducers/errors.js new file mode 100644 index 0000000000000000000000000000000000000000..ca1deac9340ea56fec52be00204db69b7b4cee82 --- /dev/null +++ b/wtwui/src/reducers/errors.js @@ -0,0 +1,18 @@ +import { GET_ERRORS } from '../actions/types' + +const initialState = { + msg: {}, + status: null, +}; + +export default function (state = initialState, action) { + switch (action.type) { + case GET_ERRORS: + return { + msg: action.payload.msg, + status: action.payload.status, + }; + default: + return state; + } +} \ No newline at end of file diff --git a/wtwui/src/reducers/index.js b/wtwui/src/reducers/index.js new file mode 100644 index 0000000000000000000000000000000000000000..11302465673c35c55f18b400db35779a6e70de03 --- /dev/null +++ b/wtwui/src/reducers/index.js @@ -0,0 +1,10 @@ +import { combineReducers } from 'redux'; +import errors from './errors'; +import messages from './messages'; +import auth from './auth'; + +export default combineReducers({ + errors, + messages, + auth, +}); \ No newline at end of file diff --git a/wtwui/src/reducers/messages.js b/wtwui/src/reducers/messages.js new file mode 100644 index 0000000000000000000000000000000000000000..3b37fa1d2f261084e9963307a933a95942e3e688 --- /dev/null +++ b/wtwui/src/reducers/messages.js @@ -0,0 +1,12 @@ +import { CREATE_MESSAGE } from '../actions/types'; + +const initialState = {}; + +export default function (state = initialState, action) { + switch (action.type) { + case CREATE_MESSAGE: + return (state = action.payload); + default: + return state; + } +} \ No newline at end of file diff --git a/wtwui/src/store.js b/wtwui/src/store.js new file mode 100644 index 0000000000000000000000000000000000000000..3943a35fe163d078b2a63882ab797a15e0e9db91 --- /dev/null +++ b/wtwui/src/store.js @@ -0,0 +1,16 @@ +import { createStore, applyMiddleware } from 'redux'; +import { composeWithDevTools } from 'redux-devtools-extension'; +import thunk from 'redux-thunk'; +import rootReducer from './reducers'; + +const initialState = {}; + +const middleware = [thunk]; + +const store = createStore( + rootReducer, + initialState, + composeWithDevTools(applyMiddleware(...middleware)), +); + +export default store; \ No newline at end of file