diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 0000000..ee8f601
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,13 @@
+[[redirects]]
+ from = "/*"
+ to = "/index.html"
+ status = 200
+
+[context.production.environment]
+ REACT_APP_SVR_API = "https://stonehaven-academy.herokuapp.com/api"
+
+[context.branch-deploy.environment]
+ REACT_APP_SVR_API = "https://stonehaven-server-staging.herokuapp.com/api"
+
+[context.deploy-preview.environment]
+ REACT_APP_SVR_API = "https://stonehaven-server-staging.herokuapp.com/api"
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 2bc6c44..99c4a16 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4640,6 +4640,28 @@
}
}
},
+ "eslint-config-airbnb": {
+ "version": "18.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.0.1.tgz",
+ "integrity": "sha512-hLb/ccvW4grVhvd6CT83bECacc+s4Z3/AEyWQdIT2KeTsG9dR7nx1gs7Iw4tDmGKozCNHFn4yZmRm3Tgy+XxyQ==",
+ "dev": true,
+ "requires": {
+ "eslint-config-airbnb-base": "^14.0.0",
+ "object.assign": "^4.1.0",
+ "object.entries": "^1.1.0"
+ }
+ },
+ "eslint-config-airbnb-base": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz",
+ "integrity": "sha512-2IDHobw97upExLmsebhtfoD3NAKhV4H0CJWP3Uprd/uk+cHuWYOczPVxQ8PxLFUAw7o3Th1RAU8u1DoUpr+cMA==",
+ "dev": true,
+ "requires": {
+ "confusing-browser-globals": "^1.0.7",
+ "object.assign": "^4.1.0",
+ "object.entries": "^1.1.0"
+ }
+ },
"eslint-config-prettier": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.4.0.tgz",
@@ -7949,6 +7971,11 @@
"object.assign": "^4.1.0"
}
},
+ "jwt-decode": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz",
+ "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk="
+ },
"killable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -9040,6 +9067,18 @@
"object-keys": "^1.0.11"
}
},
+ "object.entries": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz",
+ "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.12.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ }
+ },
"object.fromentries": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz",
diff --git a/package.json b/package.json
index 325b50b..cacd984 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"axios": "^0.19.0",
"dotenv": "^8.1.0",
"isotope-layout": "^3.0.6",
+ "jwt-decode": "^2.2.0",
"node-sass": "^4.12.0",
"prop-types": "^15.7.2",
"react": "^16.8.6",
@@ -39,6 +40,7 @@
]
},
"devDependencies": {
+ "eslint-config-airbnb": "^18.0.1",
"eslint-config-prettier": "^6.4.0"
}
}
diff --git a/src/App.js b/src/App.js
index 495fdd2..a4da35a 100644
--- a/src/App.js
+++ b/src/App.js
@@ -2,14 +2,15 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Switch, Route, Redirect } from 'react-router-dom';
import DUMMY_DATA from './DUMMY_DATA';
+import { tokenServices, ProtectedRoute } from './utils';
import {
NavBar,
HomePage,
PathPage,
LessonPage,
CatalogPage,
- // RegistrationPage,
- // DashboardPage,
+ RegistrationPage,
+ DashboardPage,
SupportPage,
AboutPage,
ErrorPage,
@@ -27,54 +28,115 @@ const links = {
dashboard: '/dashboard',
};
-function App() {
- const { courses } = DUMMY_DATA;
+class App extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ user: null,
+ isAuthenticated: false,
+ };
+ }
- return (
-
-
-
-
- }
- />
-
- {/* */}
- {/* */}
-
-
+ // componentWillMount = () => {
+ // const { history, location } = this.props;
+ // console.log(history);
+ // console.log(location);
+ // }
+
+ /**
+ * get user data from database
+ */
+ componentDidMount = () => {
+ const user = tokenServices.getToken();
+ if (user) {
+ this.setState({ isAuthenticated: true, user });
+ }
+ };
+
+ handleLogin = () => {
+ const user = tokenServices.getToken();
+ if (user) {
+ this.setState({ isAuthenticated: true, user });
+ } else {
+ this.setState({ isAuthenticated: null, user: null });
+ }
+ };
-
- {
- const courseObj = courses.find(
- course => course.slug === props.match.params.course,
- );
- const prevCourse = courses.find(course => course.order === courseObj.order - 1);
- const nextCourse = courses.find(course => course.order === courseObj.order + 1);
- const order = parseInt(props.match.params.order, 10);
- if (!courseObj || order >= courseObj.lessons.length) return ;
- return (
- {
+ tokenServices.removeToken();
+ this.setState({ isAuthenticated: false, user: null });
+ };
- />
- );
- }}
+ render() {
+ const { courses } = DUMMY_DATA;
+ const { user, isAuthenticated } = this.state;
+
+ return (
+
+
-
-
-
- );
+
+
+ }
+ />
+
+ {/* Login Protected Route */}
+
+ {/* Dashboard Protected Route */}
+
+
+
+
+
+ {
+ const courseObj = courses.find(
+ course => course.slug === props.match.params.course,
+ );
+ const prevCourse = courses.find(
+ course => course.order === courseObj.order - 1,
+ );
+ const nextCourse = courses.find(
+ course => course.order === courseObj.order + 1,
+ );
+ const order = parseInt(props.match.params.order, 10);
+ if (!courseObj || order >= courseObj.lessons.length) return ;
+ return (
+
+ );
+ }}
+ />
+
+
+
+ );
+ }
}
App.propTypes = {
diff --git a/src/components/NavBar.js b/src/components/NavBar.js
index 8d839db..9c96142 100644
--- a/src/components/NavBar.js
+++ b/src/components/NavBar.js
@@ -11,9 +11,17 @@ class NavBar extends React.Component {
this.state = {
menuOpen: false,
+ isAuthenticated: false,
};
}
+ componentDidUpdate(prevProps, prevState) {
+ const { isAuthenticated } = this.props;
+ if (isAuthenticated !== prevState.isAuthenticated) {
+ this.setState({ isAuthenticated });
+ }
+ }
+
// This keeps your state in sync with the opening/closing of the menu
// via the default means, e.g. clicking the X, pressing the ESC key etc.
handleStateChange(state) {
@@ -61,14 +69,14 @@ class NavBar extends React.Component {
Catalog
- {/* Commented out until implemented */}
About
Support
this.closeMenu()} target="_blank" rel="noopener noreferrer" href="https://forms.gle/2YMiTeQ4iuZByx4ZA">Feedback
- {/* Dashboard */}
- {/* Login */}
+ {this.state.isAuthenticated ? (Dashboard) : null}
+ {this.state.isAuthenticated ? ({this.props.handleLogoff();}} to="/">Log Off) : (
+ Login)}
@@ -107,6 +122,7 @@ NavBar.propTypes = {
links: PropTypes.shape({
home: PropTypes.string,
}).isRequired,
+ isAuthenticated: PropTypes.bool,
};
export default NavBar;
diff --git a/src/components/dashboard/DashboardMenu.js b/src/components/dashboard/DashboardMenu.js
new file mode 100644
index 0000000..5368a3f
--- /dev/null
+++ b/src/components/dashboard/DashboardMenu.js
@@ -0,0 +1,50 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+/**
+ * Dashboard navigation menu
+ * @param {Object} props Passed props
+ * @param {string} activeView Active navigation text
+ * @param {Function} showOverview Display function handler
+ * @param {Function} showSettings Display function handler
+ * @param {Function} logout Logout function handler
+ */
+const DashboardMenu = ({ activeView, showOverview, showSettings, logout }) => (
+
+
My Dashboard
+
+ -
+ {activeView === 'overview' ? (
+ Overview
+ ) : (
+
+ )}
+
+ -
+ {activeView === 'settings' ? (
+ Account Settings
+ ) : (
+
+ )}
+
+ {/* -
+
+
*/}
+
+
+);
+
+DashboardMenu.propTypes = {
+ activeView: PropTypes.string.isRequired,
+ showOverview: PropTypes.func.isRequired,
+ showSettings: PropTypes.func.isRequired,
+ logout: PropTypes.func,
+};
+
+export default DashboardMenu;
diff --git a/src/components/pages/DashboardPage.js b/src/components/pages/DashboardPage.js
index bdc0c71..d3a38a2 100644
--- a/src/components/pages/DashboardPage.js
+++ b/src/components/pages/DashboardPage.js
@@ -1,7 +1,71 @@
import React from 'react';
+import tokenService from '../../utils/tokenServices';
+import DashboardMenu from '../dashboard/DashboardMenu';
+import '../../css/pages/DashboardPage.scss';
-const DashboardPage = () => (
- I am a dashboard page that will route to the correct dashboard type!
-);
+
+class DashboardPage extends React.Component{
+ state = {
+ userName: '',
+ userID: '',
+ userPersona: '',
+ active: 'overview',
+ }
+
+ componentDidMount(){
+ const token = tokenService.getToken();
+ if(!token){
+ // Redirect to HTTP ErrorCode 500 page, this is a placeholder, replace with real
+ this.props.history.push('/login');
+ } else {
+ this.setState({userID: token.id});
+ this.setState({userPersona: token.persona});
+ this.setState({userName: token.fullName});
+ }
+ }
+
+ goToOverview = () => {
+ this.setState({ active: 'overview' });
+ };
+
+ goToSettings = () => {
+ this.setState({ active: 'settings' });
+ };
+
+ render(){
+ return (
+
+
+ {this.state.active === 'overview' ? (
+
+
+ {`Welcome, ${this.state.userName || 'Anon'}`}
+
+
+ ) : (
+ ''
+ )}
+ {this.state.active === 'settings' ? (
+
+
+ {this.state.userName}
+'s Account
+
+
+ {`User ID: ${this.state.userID}`}
+
+
+ ) : (
+ ''
+ )}
+
+ );
+ }
+}
export default DashboardPage;
diff --git a/src/components/pages/HomePage.js b/src/components/pages/HomePage.js
index eec8f96..c7133fc 100644
--- a/src/components/pages/HomePage.js
+++ b/src/components/pages/HomePage.js
@@ -1,5 +1,4 @@
import React from 'react';
-import { NavLink } from 'react-router-dom';
import '../../css/pages/HomePage.scss';
// import Glide from '@glidejs/glide/';
diff --git a/src/components/pages/RegistrationPage.js b/src/components/pages/RegistrationPage.js
index ff45702..fdf16eb 100644
--- a/src/components/pages/RegistrationPage.js
+++ b/src/components/pages/RegistrationPage.js
@@ -1,7 +1,8 @@
import React, { Component } from 'react';
+import PropTypes from 'prop-types';
import LogIn from '../registration/LogIn';
import SignUp from '../registration/SignUp';
-
+import TokenServices from '../../utils/tokenServices'
import '../../css/pages/RegistrationPage.scss';
class RegistrationPage extends Component {
@@ -13,6 +14,25 @@ class RegistrationPage extends Component {
}
}
+ /**
+ * Checks wether or not the user is already logged in
+ */
+ componentDidMount() {
+ if(TokenServices.getToken()){
+ // console.log("You are logged in redirecting")
+ // this.props.history.push("/dashboard");
+ this.handleLogin()
+ }else{
+ // console.log("You are not logged in doing nothing")
+ }
+ }
+
+ handleLogin = event => {
+ // event.preventDefault();
+ // this.props.history.push("/dashboard");
+ this.props.handleLogin();
+ }
+
changeToSignup = event => {
event.preventDefault();
this.setState({ hideSignUp: false });
@@ -28,11 +48,15 @@ class RegistrationPage extends Component {
return (
{hideSignUp
- ? this.changeToSignup} />
- : this.backToLogin} /> }
+ ? this.changeToSignup} handleLogin={this.handleLogin}/>
+ : this.backToLogin} handleSignup={this.handleLogin}/> }
)
}
}
+RegistrationPage.propTypes = {
+ handleLogin: PropTypes.func.isRequired,
+};
+
export default RegistrationPage;
diff --git a/src/components/registration/LogIn.js b/src/components/registration/LogIn.js
index 486e508..069e254 100644
--- a/src/components/registration/LogIn.js
+++ b/src/components/registration/LogIn.js
@@ -1,65 +1,90 @@
-import React,{Component} from 'react';
-import Axios from 'axios';
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import axios from 'axios';
import '../../css/registration/LogIn.scss';
+const { REACT_APP_SVR_API } = process.env;
-class LogIn extends Component{
+class LogIn extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
- }
+ };
}
- LogInHandler = () =>{
+ LogInHandler = () => {
const { email, password } = this.state;
const data = {
email,
password,
- }
-
- Axios.post('http://localhost:5001/api/user/login',data)
- .then(response=>{
+ };
+
+ axios
+ .post(`${REACT_APP_SVR_API}/user/login`, data)
+ .then(response => {
localStorage.setItem('app-token', response.data.token);
+ // this.props.history.push("/dashboard");
+ this.props.handleLogin();
})
- .catch(error=>{
- this.showSuccess = false;
- this.showError = true;
- try{
- if(error.response.status === 400){
- console.log('Bad Request');
- }else if(error.response.status === 500){
- console.log('Something bad happended on the server.')
- }else{
- console.log('Uh oh...')
+ .catch(error => {
+ try {
+ // Handles errors that are not HTTP specific
+ console.error(error);
+ this.setState({ showRegistrationFailure: true });
+ if (!error.status) {
+ console.error('A network error has occured.');
+ } else if (error.response.status === 400) {
+ console.error('Bad Request');
+ } else if (error.response.status === 500) {
+ console.error('Something bad happended on the server.');
+ } else {
+ console.error('An unknown error has occurred');
}
- }catch(ex){
- this.errors = error.response.data;
- Promise.reject(error);
+ } catch (ex) {
+ Promise.reject(ex);
}
- })
- }
+ });
+ };
- render(){
+ render() {
const { email, password } = this.state;
const { changeToSignup } = this.props;
return (
-
Login
+
Login
-
Don't have an account,
+
+ {`Don't have an account, `}
+
+
);
}
}
+LogIn.propTypes = {
+ handleLogin: PropTypes.func.isRequired,
+};
-export default LogIn;
\ No newline at end of file
+export default LogIn;
diff --git a/src/components/registration/SignUp.js b/src/components/registration/SignUp.js
index 9cce2f8..fea739b 100644
--- a/src/components/registration/SignUp.js
+++ b/src/components/registration/SignUp.js
@@ -1,9 +1,26 @@
import React, { Component } from 'react';
-import Axios from 'axios';
+import PropTypes from 'prop-types';
+import axios from 'axios';
import '../../css/registration/SignUp.scss';
+const { REACT_APP_SVR_API } = process.env;
+
+/**
+ * @description Validates all inputs to theses constraints
+ */
+const validate = (email, password, confirmedPassword, fullName, location) => ({
+ email: email.length === 0,
+ password: password.length < 8 || password !== confirmedPassword,
+ fullName: fullName.length === 0,
+ location: location.length === 0,
+});
+
+const WarningBanner = warn_data =>
+ // eslint-disable-next-line implicit-arrow-linebreak
+ warn_data.warn ? {warn_data.message}
: null;
+
class SignUp extends Component {
- constructor(props){
+ constructor(props) {
super(props);
this.state = {
@@ -13,73 +30,51 @@ class SignUp extends Component {
confirmPassword: '',
location: '',
fullName: '',
- // Might have to refactored this...
+ // Might have to refactor this...
touched: {
email: false,
password: false,
},
- // Controls when there are failed validations when registering
- disableButton: true,
- // Modal
- showModal: false,
- // Shows error message if server fails
- showRegistrationFailure: false,
- // Shows when waiting for server response
- showSpinner: false,
- successfulRegister: false,
- }
+ };
}
-
-
- /**
- * Controls modal and spinner controlls
- */
- showModal = () => {
- this.setState({ showModal: true });
- };
-
- hideModal = () => {
- this.setState({ showModal: false });
- };
-
- showSpinner = () => {
- this.setState({ loading: true });
- };
-
- hideSpinner = () => {
- this.setState({ loading: false });
- };
/**
* Creates a user with the server
*/
createUserHandler = () => {
const { email, password, location, fullName } = this.state;
+ const { handleSignup } = this.props;
const data = {
email,
password,
location,
fullName,
};
- Axios.post('http://localhost:5001/api/user/', data)
+ axios
+ .post(`${REACT_APP_SVR_API}/user/`, data)
.then(response => {
- console.log(response);
+ // console.log(response);
// Stores token in local storeage for the time being
localStorage.setItem('app-token', response.data.token);
// Sweet Alert for successful registration
- // Redirect to dashboard
+ handleSignup();
})
.catch(error => {
- this.setState({ showRegistrationFailure: true });
try {
- if (error.response.status === 400) {
- console.log('Bad Request');
+ // Handles errors that are not HTTP specific
+ console.error(error);
+ this.setState({ showRegistrationFailure: true });
+ if (!error.status) {
+ console.error('A network error has occured.');
+ } else if (error.response.status === 400) {
+ console.error('Bad Request');
} else if (error.response.status === 500) {
- console.log('Something bad happended on the server.');
+ console.error('Something bad happended on the server.');
} else {
- console.log('Uh oh...');
+ console.error('An unknown error has occurred');
}
} catch (ex) {
+ alert('Something went wrong...');
Promise.reject(ex);
}
});
@@ -108,11 +103,11 @@ class SignUp extends Component {
};
handleBlur = field => event => {
- this.setState({
- touched: { ...this.state.touched, [field]: true },
- });
+ this.setState(prevState => ({
+ touched: { ...prevState.touched, [field]: true },
+ }));
};
-
+
/**
* @description Controls the submit button
*/
@@ -126,38 +121,30 @@ class SignUp extends Component {
};
canBeSubmitted() {
- const { email, password, confirmPassword, location, fullName } = this.state;
+ const { email, password, confirmPassword, location, fullName } = this.state;
const errors = validate(
email,
password,
- confirmPassword,
+ confirmPassword, // Moda
fullName,
- location
+ location,
);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return !isDisabled;
}
-
render() {
- const { email, password, confirmPassword, location, fullName } = this.state;
+ const { email, password, confirmPassword, location, fullName, touched } = this.state;
const { backToLogin } = this.props;
- const errors = validate(
- email,
- password,
- confirmPassword,
- fullName,
- location
- );
+ const errors = validate(email, password, confirmPassword, fullName, location);
const isDisabled = Object.keys(errors).some(x => errors[x]);
const shouldMarkError = field => {
const hasError = errors[field];
- const shouldShow = this.state.touched[field];
+ const shouldShow = touched[field];
return hasError ? shouldShow : false;
};
-
return (
Register
@@ -166,25 +153,25 @@ class SignUp extends Component {
Password
Confirm Password
@@ -192,33 +179,38 @@ class SignUp extends Component {
Location
-
- By clicking "Sign Up" you are agreeing
- to our Terms and Agreement
-
+ {/*
+ {`By clicking "Sign Up" you are agreeing to our `}
+ Terms and Agreement
+
*/}
-
Go back to
+
+ {`Go back to `}
+
+
({
- email: email.length === 0,
- password: password.length < 8 || password !== confirmedPassword,
- fullName: fullName.length === 0,
- location: location.length === 0,
-});
-
-function WarningBanner(props) {
- if (!props.warn) {
- return null;
- }
- return {props.message}
;
-}
-
+SignUp.propTypes = {
+ handleSignup: PropTypes.func.isRequired,
+};
export default SignUp;
diff --git a/src/css/pages/DashboardPage.scss b/src/css/pages/DashboardPage.scss
index 07f22e3..18e1df8 100644
--- a/src/css/pages/DashboardPage.scss
+++ b/src/css/pages/DashboardPage.scss
@@ -1,3 +1,149 @@
-#an-id {
+//TODO: Resolve class names to be camelCase or kabob-case
+
+.dashboard {
+ display: flex;
+ min-height: calc(100vh - 80px);
+}
+
+@media screen and (max-width: 860px) {
+ .dashboard {
+ flex-direction: column;
+ align-items: stretch;
+ text-align: center;
+ }
+
+ .accountForms form {
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .accountForms form label input {
+ margin: 10px 0;
+ }
+
+ .accountForms form button {
+ margin: 20px auto;
+ width: 100%;
+ max-width: 2000px;
+ }
+}
+
+.dashboardHeader, .menuHeader{
+ display: block;
+ background-color: transparent;
+ border: none;
+ font-size: 1.5rem;
+ font-family: var(--fontfamily-alt);
+ padding: 0;
+ float: none;
+ margin-left: 0;
+}
+
+.menuHeader{
+ color: var(--white);
+ margin-bottom: 10px;
+ font-family: var(--fontfamily);
+}
+
+.dashboardHeader, .dashboardText{
+ color: var(--black);
+}
+
+.dashboardBody {
+ padding: 50px;
+ flex: 1;
+}
+
+.dashboardBody p {
+ padding: 15px 0;
+ margin: 0;
+ font-family: var(--fontfamily-roboto);
+}
+
+.dashboard .activeView {
+ font-weight: bolder;
+ font-family: var(--fontfamily-roboto);
+ color: var(--greyblue);
+}
+
+.accountMenu {
+ padding: 30px;
+ background-color: var(--light-blue);
+}
+
+.accountMenu ul {
+ list-style: none;
+ padding: 0;
+}
+
+.accountMenu ul li {
+ margin-bottom: 15px;
+}
+
+.accountMenu .activeView {
+ color: var(--orange);
+}
+
+.linkButton {
+ background-color: transparent;
+ border: none;
+ cursor: pointer;
+ display: inline;
+ margin: 0;
+ padding: 0;
+ font-size: 18px;
+ font-family: var(--fontfamily-roboto);
+ color: var(--white);
+}
+
+.linkButton:hover,
+.linkButton:focus {
+ text-decoration: underline;
+}
+
+.accountSettings {
+ display: flex;
+ flex-direction: column;
+}
+
+.accountForms {
+ display: flex;
+ justify-content: space-around;
+ flex-wrap: wrap;
+}
+
+.accountForms form {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ margin: 0;
+ padding-right: 30px;
+}
+
+.accountForms form input {
+ display: block;
+ margin: 10px;
+ padding-left: 20px;
+ min-width: 200px;
+ max-width: 400px;
+ background-color: var(--greyblue);
outline: none;
+ font-size: 1rem;
+ font-family: var(--fontfamily-roboto);
+}
+
+.accountForms form button {
+ font-size: 18px;
+ margin: 20px 0;
+ max-width: 200px;
+ font-family: var(--fontfamily);
+ background-color: var(--light-blue);
+ outline: none;
+}
+
+.accountForms form button:active {
+ outline: 1px solid var(--grey);
+ outline-offset: -4px;
}
diff --git a/src/utils/ProtectedRoute.js b/src/utils/ProtectedRoute.js
new file mode 100644
index 0000000..593f99e
--- /dev/null
+++ b/src/utils/ProtectedRoute.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Route, Redirect } from 'react-router-dom';
+
+/**
+ * Higher Order Component that wraps the Router component to obfuscate authentication in routes.
+ * @param {Object} props Props Variables
+ * @param {Symbol} props.component Rendered component
+ * @param {String} props.redirectLink Link for default route
+ * @param {bool} props.isAuthenticated A boolean flag
+ */
+const ProtectedDashboardRoute = ({ component: Component, isAuthenticated, redirectLink, ...rest }) => (
+
+ isAuthenticated ? (
+
+ ) : (
+
+ )
+ }
+ />
+);
+
+ProtectedDashboardRoute.propTypes = {
+ component: PropTypes.func.isRequired,
+ isAuthenticated: PropTypes.bool.isRequired,
+ redirectLink: PropTypes.string.isRequired,
+ location: PropTypes.object, // FIXME: Check out how to get this to not yell with .isRequired
+};
+
+export default ProtectedDashboardRoute;
diff --git a/src/utils/index.js b/src/utils/index.js
index 8ab2346..63becc3 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -1,2 +1,4 @@
export { FontAwesomeIcon } from './fontAwesome';
export { default as userAPI } from './userAPI';
+export { default as tokenServices } from './tokenServices';
+export { default as ProtectedRoute } from './ProtectedRoute';
diff --git a/src/utils/tokenServices.js b/src/utils/tokenServices.js
new file mode 100644
index 0000000..bb708d1
--- /dev/null
+++ b/src/utils/tokenServices.js
@@ -0,0 +1,23 @@
+const jwtdecode = require('jwt-decode');
+/**
+ * Gets the token from local storage and decodes the information needed
+ */
+function getToken() {
+ const token = localStorage.getItem('app-token');
+ if(!token){
+ return null;
+ }
+ const results = jwtdecode(token);
+ return {id: results.id, persona: results.persona, fullName: results.fullName}
+}
+/**
+ * Removes token from local storage ie: logout
+ */
+function removeToken() {
+ localStorage.removeItem('app-token');
+}
+
+module.exports = {
+ getToken,
+ removeToken,
+}
\ No newline at end of file