diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fdd69ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2020, Telecom Infra Project +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index be04c51..22bd023 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,23 @@ # TIP WLAN Cloud Portal ## Set up environment: - - Install Dependencies - `npm install` - To link cu-ui package locally for development: - `npm link ../cu-ui` + Install Dependencies + `npm install` + +To link wlan-cloud-ui-library package locally for development: +`npm link ../wlan-cloud-ui-library` ## Run: - ### Development + +### Development + `npm start` - ### Tests +### Tests + `npm run test` - ### Production +### Production + `npm run build` diff --git a/app/containers/App/components/ProtectedRouteWithLayout.js b/app/containers/App/components/ProtectedRouteWithLayout.js index 3aa66e3..1a95dfa 100644 --- a/app/containers/App/components/ProtectedRouteWithLayout.js +++ b/app/containers/App/components/ProtectedRouteWithLayout.js @@ -3,9 +3,10 @@ import T from 'prop-types'; import { Route, Redirect } from 'react-router-dom'; import MasterLayout from 'containers/MasterLayout'; -import { getItem } from 'utils/localStorage'; import { AUTH_TOKEN } from 'constants/index'; +import { getItem } from 'utils/localStorage'; + const ProtectedRouteWithLayout = ({ component: Component, ...rest }) => ( ( diff --git a/app/containers/Login/index.js b/app/containers/Login/index.js new file mode 100644 index 0000000..145947c --- /dev/null +++ b/app/containers/Login/index.js @@ -0,0 +1,33 @@ +import React from 'react'; +import gql from 'graphql-tag'; +import { useMutation, useApolloClient } from '@apollo/react-hooks'; + +import { Login as LoginPage } from 'wlan-cloud-ui-library'; + +import { AUTH_TOKEN } from 'constants/index'; + +import { setItem } from 'utils/localStorage'; + +const AUTHENTICATE_USER = gql` + mutation AuthenticateUser($email: String!, $password: String!) { + authenticateUser(email: $email, password: $password) { + token + } + } +`; + +const Login = () => { + const client = useApolloClient(); + const [authenticateUser] = useMutation(AUTHENTICATE_USER); + + const handleLogin = (email, password) => { + authenticateUser({ variables: { email, password } }).then(({ data }) => { + client.resetStore(); + setItem(AUTH_TOKEN, data.authenticateUser.token, data.authenticateUser.token.expires_in); + }); + }; + + return ; +}; + +export default Login; diff --git a/app/containers/MasterLayout/index.js b/app/containers/MasterLayout/index.js index 934ba31..8df79df 100644 --- a/app/containers/MasterLayout/index.js +++ b/app/containers/MasterLayout/index.js @@ -1,13 +1,25 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useLocation } from 'react-router-dom'; +import { useApolloClient } from '@apollo/react-hooks'; -import { AppLayout as Layout } from 'cu-ui'; +import { AppLayout as Layout } from 'wlan-cloud-ui-library'; + +import { AUTH_TOKEN } from 'constants/index'; + +import { removeItem } from 'utils/localStorage'; const MasterLayout = ({ children }) => { + const client = useApolloClient(); const location = useLocation(); + + const handleLogout = () => { + removeItem(AUTH_TOKEN); + client.resetStore(); + }; + return ( - {}} locationState={location}> + {children} ); diff --git a/app/index.js b/app/index.js index d82d2ae..56c4f95 100644 --- a/app/index.js +++ b/app/index.js @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom'; import { BrowserRouter as Router } from 'react-router-dom'; import ApolloClient from 'apollo-boost'; import { ApolloProvider } from '@apollo/react-hooks'; +import gql from 'graphql-tag'; import 'styles/antd.less'; import 'styles/index.scss'; @@ -10,27 +11,65 @@ import 'styles/index.scss'; import App from 'containers/App'; import { AUTH_TOKEN } from 'constants/index'; +import { getItem } from 'utils/localStorage'; + const MOUNT_NODE = document.getElementById('root'); +const REFRESH_TOKEN = gql` + mutation UpdateToken($refreshToken: String!) { + updateToken(refreshToken: $refreshToken) { + token + } + } +`; + const client = new ApolloClient({ - // uri: "" + uri: 'http://localhost:4000/', request: operation => { - const token = localStorage.getItem(AUTH_TOKEN); + const token = getItem(AUTH_TOKEN); operation.setContext({ headers: { - authorization: token ? `Bearer ${token}` : '', + authorization: token ? `Bearer ${token.access_token}` : '', }, }); }, + onError: ({ graphQLErrors, operation, forward }) => { + if (graphQLErrors) { + graphQLErrors.forEach(err => { + // handle errors differently based on its error code + switch (err.extensions.code) { + case 'UNAUTHENTICATED': + operation.setContext({ + headers: { + ...operation.getContext().headers, + authorization: client + .mutate({ + mutation: REFRESH_TOKEN, + variables: { + refreshToken: getItem(AUTH_TOKEN).refresh_token, + }, + }) + .then(data => { + return data.updateToken.token; + }), + }, + }); + return forward(operation); + default: + return forward(operation); + } + }); + } + }, }); const render = () => { ReactDOM.render( - - + + - - , + + , MOUNT_NODE ); }; diff --git a/app/utils/localStorage.js b/app/utils/localStorage.js index dafc6a8..789ead1 100644 --- a/app/utils/localStorage.js +++ b/app/utils/localStorage.js @@ -22,17 +22,15 @@ export const removeItem = key => { }; export const getItem = key => { - let value; + let value = null; try { - value = JSON.parse(window.localStorage.getItem(key) || '{}'); + value = JSON.parse(window.localStorage.getItem(key)); } catch (err) { - removeItem(); - - return {}; + return null; } if (isItemExpired(value)) { - return removeItem(); + return removeItem(key); } return value; diff --git a/package-lock.json b/package-lock.json index 2c47e18..6130d28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "cu-portal", + "name": "wlan-cloud-ui", "version": "0.1.0", "lockfileVersion": 1, "requires": true, @@ -49,6 +49,63 @@ "tslib": "^1.10.0" } }, + "@apollo/react-components": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@apollo/react-components/-/react-components-3.1.4.tgz", + "integrity": "sha512-dLZFeJ+x48nPuo2BjFjfbl8afQyazoqU8xBTidMnYy99w9DcTHtnURTw18o2dT5jkfuIJEWjbTfxyjJnHk+clw==", + "requires": { + "@apollo/react-common": "^3.1.4", + "@apollo/react-hooks": "^3.1.4", + "prop-types": "^15.7.2", + "ts-invariant": "^0.4.4", + "tslib": "^1.10.0" + }, + "dependencies": { + "@apollo/react-common": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@apollo/react-common/-/react-common-3.1.4.tgz", + "integrity": "sha512-X5Kyro73bthWSCBJUC5XYQqMnG0dLWuDZmVkzog9dynovhfiVCV4kPSdgSIkqnb++cwCzOVuQ4rDKVwo2XRzQA==", + "requires": { + "ts-invariant": "^0.4.4", + "tslib": "^1.10.0" + } + }, + "@apollo/react-hooks": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@apollo/react-hooks/-/react-hooks-3.1.4.tgz", + "integrity": "sha512-yamD5a1Gu9fGQjZYEQn2nSG+BRyde4dy055agtYOvP/5/njJBSJ25tRFbjxKf7+YW9fsu2Xguib3anoQTuesNg==", + "requires": { + "@apollo/react-common": "^3.1.4", + "@wry/equality": "^0.1.9", + "ts-invariant": "^0.4.4", + "tslib": "^1.10.0" + } + } + } + }, + "@apollo/react-hoc": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@apollo/react-hoc/-/react-hoc-3.1.4.tgz", + "integrity": "sha512-dCzVM2/JzEWqwTocwozb9/msOgVY5EG7gh593IIXYcBfHcthSK21nIsbH/uDgwCcLSIZE2EOC3n0aKgDuoMtjA==", + "requires": { + "@apollo/react-common": "^3.1.4", + "@apollo/react-components": "^3.1.4", + "hoist-non-react-statics": "^3.3.0", + "ts-invariant": "^0.4.4", + "tslib": "^1.10.0" + }, + "dependencies": { + "@apollo/react-common": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@apollo/react-common/-/react-common-3.1.4.tgz", + "integrity": "sha512-X5Kyro73bthWSCBJUC5XYQqMnG0dLWuDZmVkzog9dynovhfiVCV4kPSdgSIkqnb++cwCzOVuQ4rDKVwo2XRzQA==", + "requires": { + "ts-invariant": "^0.4.4", + "tslib": "^1.10.0" + } + } + } + }, "@apollo/react-hooks": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@apollo/react-hooks/-/react-hooks-3.1.3.tgz", @@ -3650,10 +3707,6 @@ } } }, - "cu-ui": { - "version": "git+https://sean_macfarlane@bitbucket.org/connectustechnologies/connectus-wlan-ui-workspace.git#c7be790677869070cb390411bdd4ba453e788606", - "from": "git+https://sean_macfarlane@bitbucket.org/connectustechnologies/connectus-wlan-ui-workspace.git" - }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -6097,6 +6150,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" + } + }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -13562,6 +13623,10 @@ } } }, + "wlan-cloud-ui-library": { + "version": "git+ssh://git@github.com/Telecominfraproject/wlan-cloud-ui-library.git#abbb7f2c8eaaeec30662331c0aed190be66c0de6", + "from": "git+ssh://git@github.com/Telecominfraproject/wlan-cloud-ui-library.git" + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/package.json b/package.json index 80da5a8..34cc91b 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "cu-portal", + "name": "wlan-cloud-ui", "version": "0.1.0", "author": "ConnectUs", - "description": "", + "description": "React Portal", "engines": { "npm": ">=5", "node": ">=8" @@ -17,12 +17,14 @@ "license": "MIT", "dependencies": { "@ant-design/icons": "^4.0.2", + "@apollo/react-hoc": "^3.1.4", "@apollo/react-hooks": "^3.1.3", "antd": "^4.0.2", "apollo-boost": "^0.4.7", "clean-webpack-plugin": "^3.0.0", - "cu-ui": "git+https://sean_macfarlane@bitbucket.org/connectustechnologies/connectus-wlan-ui-workspace.git", + "wlan-cloud-ui-library": "git+ssh://git@github.com/Telecominfraproject/wlan-cloud-ui-library.git", "graphql": "^14.6.0", + "graphql-tag": "^2.10.3", "html-webpack-plugin": "^3.2.0", "mini-css-extract-plugin": "^0.9.0", "optimize-css-assets-webpack-plugin": "^5.0.3", diff --git a/webpack/webpack.dev.js b/webpack/webpack.dev.js index f4083f6..a5c3ecf 100644 --- a/webpack/webpack.dev.js +++ b/webpack/webpack.dev.js @@ -43,12 +43,19 @@ module.exports = { modules: [ 'node_modules', 'app', - path.resolve(__dirname, '../', 'node_modules', 'cu-ui', 'src'), + path.resolve(__dirname, '../', 'node_modules', 'wlan-cloud-ui-library', 'src'), ], alias: { app: path.resolve(__dirname, '../', 'app'), react: path.resolve(__dirname, '../', 'node_modules', 'react'), - 'cu-ui': path.resolve(__dirname, '../', 'node_modules', 'cu-ui', 'src'), + 'react-router-dom': path.resolve('./node_modules/react-router-dom'), + 'wlan-cloud-ui-library': path.resolve( + __dirname, + '../', + 'node_modules', + 'wlan-cloud-ui-library', + 'src' + ), }, }, };