Merge branch 'context-rework' into 'master'

0.0.7 - Redux purge! Move to Context

See merge request idanoo/go-scrobble!1
This commit is contained in:
Daniel Mason 2021-03-31 06:16:44 +00:00
commit 8be4a190a6
33 changed files with 521 additions and 1058 deletions

View File

@ -3,11 +3,12 @@ stages:
- bundle - bundle
variables: variables:
VERSION: 0.0.6 VERSION: 0.0.7
build-go: build-go:
image: golang:1.16.2 image: golang:1.16.2
stage: build stage: build
only: master
script: script:
- go build -o goscrobble cmd/go-scrobble/*.go - go build -o goscrobble cmd/go-scrobble/*.go
artifacts: artifacts:
@ -21,6 +22,7 @@ build-go:
build-react: build-react:
image: node:15.12.0 image: node:15.12.0
stage: build stage: build
only: master
script: script:
- cd web - cd web
- npm install - npm install
@ -33,6 +35,7 @@ build-react:
bundle: bundle:
image: bash:latest image: bash:latest
stage: bundle stage: bundle
only: master
variables: variables:
GIT_STRATEGY: none GIT_STRATEGY: none
before_script: before_script:

View File

@ -1,3 +1,8 @@
# 0.0.7
- Switch redux -> Context
- Remove excess packages
# 0.0.6 # 0.0.6
- Fix hitting dashboard when logged out - Fix hitting dashboard when logged out
- Clean up app.js - Clean up app.js

View File

@ -61,10 +61,8 @@ func createUser(req *RegisterRequest, ip net.IP) error {
// Check username is valid // Check username is valid
if !isUsernameValid(req.Username) { if !isUsernameValid(req.Username) {
log.Println("user is invalid")
return errors.New("Username contains invalid characters") return errors.New("Username contains invalid characters")
} }
log.Println("user is valid")
// If set an email.. validate it! // If set an email.. validate it!
if req.Email != "" { if req.Email != "" {

108
web/package-lock.json generated
View File

@ -19,21 +19,13 @@
"react": "^17.0.2", "react": "^17.0.2",
"react-bootstrap": "^1.5.2", "react-bootstrap": "^1.5.2",
"react-cookie": "^4.0.3", "react-cookie": "^4.0.3",
"react-data-grid": "*",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-redux": "^7.2.3", "react-redux": "^7.2.3",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"react-spinners": "^0.10.6", "react-spinners": "^0.10.6",
"react-table": "^7.6.3",
"react-toast": "^1.0.1",
"react-toast-notifications": "^2.4.3",
"react-toastify": "^7.0.3", "react-toastify": "^7.0.3",
"reactstrap": "^8.9.0", "reactstrap": "^8.9.0"
"redux": "^4.0.5",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
"web-vitals": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"redux-devtools-extension": "^2.13.9", "redux-devtools-extension": "^2.13.9",
@ -16328,18 +16320,6 @@
"react": ">= 16.3.0" "react": ">= 16.3.0"
} }
}, },
"node_modules/react-data-grid": {
"version": "7.0.0-canary.38",
"resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-7.0.0-canary.38.tgz",
"integrity": "sha512-JjMyChuh9KxOtYmpxrOuPBI6EYIbNLn/+pjwoQYeD7d5vkWMURWWhyLX1NJkT5bt5LF2qxOSQiFf3G6YndxlAg==",
"dependencies": {
"clsx": "^1.1.1"
},
"peerDependencies": {
"react": "^16.14 || ^17.0",
"react-dom": "^16.14 || ^17.0"
}
},
"node_modules/react-dev-utils": { "node_modules/react-dev-utils": {
"version": "11.0.4", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
@ -16733,39 +16713,6 @@
"react-dom": "^16.0.0 || ^17.0.0" "react-dom": "^16.0.0 || ^17.0.0"
} }
}, },
"node_modules/react-table": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/react-table/-/react-table-7.6.3.tgz",
"integrity": "sha512-hfPF13zDLxPMpLKzIKCE8RZud9T/XrRTsaCIf8zXpWZIZ2juCl7qrGpo3AQw9eAetXV5DP7s2GDm+hht7qq5Dw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^16.8.3 || ^17.0.0-0"
}
},
"node_modules/react-toast": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-toast/-/react-toast-1.0.1.tgz",
"integrity": "sha512-xqkO5ZJiJDOLxycZts7xi729blKw4frhg2I4bcIjT7mVlshxu0AsaHlKAdPhVeACAnn8nnamxg6zoYoC9BEjjg==",
"peerDependencies": {
"react": ">=16"
}
},
"node_modules/react-toast-notifications": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/react-toast-notifications/-/react-toast-notifications-2.4.3.tgz",
"integrity": "sha512-Ya/i2dCjN95Ytb/pwbAVmDMSKQwGeeGOhUThtjFQx2XAFKE+fQnodLlIylhgZfsInxdUXPFGFnzTdGS8JafuLA==",
"dependencies": {
"@emotion/core": "^10.0.14",
"react-transition-group": "^4.4.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
}
},
"node_modules/react-toastify": { "node_modules/react-toastify": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.3.tgz", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.3.tgz",
@ -17011,14 +16958,6 @@
"redux": "^3.1.0 || ^4.0.0" "redux": "^3.1.0 || ^4.0.0"
} }
}, },
"node_modules/redux-persist": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
"integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
"peerDependencies": {
"redux": ">4.0.0"
}
},
"node_modules/redux-thunk": { "node_modules/redux-thunk": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
@ -20691,11 +20630,6 @@
"minimalistic-assert": "^1.0.0" "minimalistic-assert": "^1.0.0"
} }
}, },
"node_modules/web-vitals": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-1.1.1.tgz",
"integrity": "sha512-jYOaqu01Ny1NvMwJ3dBJDUOJ2PGWknZWH4AUnvFOscvbdHMERIKT2TlgiAey5rVyfOePG7so2JcXXZdSnBvioQ=="
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
@ -35007,14 +34941,6 @@
"universal-cookie": "^4.0.0" "universal-cookie": "^4.0.0"
} }
}, },
"react-data-grid": {
"version": "7.0.0-canary.38",
"resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-7.0.0-canary.38.tgz",
"integrity": "sha512-JjMyChuh9KxOtYmpxrOuPBI6EYIbNLn/+pjwoQYeD7d5vkWMURWWhyLX1NJkT5bt5LF2qxOSQiFf3G6YndxlAg==",
"requires": {
"clsx": "^1.1.1"
}
},
"react-dev-utils": { "react-dev-utils": {
"version": "11.0.4", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
@ -35331,27 +35257,6 @@
"@emotion/core": "^10.0.35" "@emotion/core": "^10.0.35"
} }
}, },
"react-table": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/react-table/-/react-table-7.6.3.tgz",
"integrity": "sha512-hfPF13zDLxPMpLKzIKCE8RZud9T/XrRTsaCIf8zXpWZIZ2juCl7qrGpo3AQw9eAetXV5DP7s2GDm+hht7qq5Dw==",
"requires": {}
},
"react-toast": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-toast/-/react-toast-1.0.1.tgz",
"integrity": "sha512-xqkO5ZJiJDOLxycZts7xi729blKw4frhg2I4bcIjT7mVlshxu0AsaHlKAdPhVeACAnn8nnamxg6zoYoC9BEjjg==",
"requires": {}
},
"react-toast-notifications": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/react-toast-notifications/-/react-toast-notifications-2.4.3.tgz",
"integrity": "sha512-Ya/i2dCjN95Ytb/pwbAVmDMSKQwGeeGOhUThtjFQx2XAFKE+fQnodLlIylhgZfsInxdUXPFGFnzTdGS8JafuLA==",
"requires": {
"@emotion/core": "^10.0.14",
"react-transition-group": "^4.4.1"
}
},
"react-toastify": { "react-toastify": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.3.tgz", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.3.tgz",
@ -35543,12 +35448,6 @@
"dev": true, "dev": true,
"requires": {} "requires": {}
}, },
"redux-persist": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
"integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
"requires": {}
},
"redux-thunk": { "redux-thunk": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
@ -38488,11 +38387,6 @@
"minimalistic-assert": "^1.0.0" "minimalistic-assert": "^1.0.0"
} }
}, },
"web-vitals": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-1.1.1.tgz",
"integrity": "sha512-jYOaqu01Ny1NvMwJ3dBJDUOJ2PGWknZWH4AUnvFOscvbdHMERIKT2TlgiAey5rVyfOePG7so2JcXXZdSnBvioQ=="
},
"webidl-conversions": { "webidl-conversions": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",

View File

@ -14,21 +14,13 @@
"react": "^17.0.2", "react": "^17.0.2",
"react-bootstrap": "^1.5.2", "react-bootstrap": "^1.5.2",
"react-cookie": "^4.0.3", "react-cookie": "^4.0.3",
"react-data-grid": "*",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-redux": "^7.2.3", "react-redux": "^7.2.3",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"react-spinners": "^0.10.6", "react-spinners": "^0.10.6",
"react-table": "^7.6.3",
"react-toast": "^1.0.1",
"react-toast-notifications": "^2.4.3",
"react-toastify": "^7.0.3", "react-toastify": "^7.0.3",
"reactstrap": "^8.9.0", "reactstrap": "^8.9.0"
"redux": "^4.0.5",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
"web-vitals": "^1.1.1"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -1,18 +0,0 @@
import ApiService from "../Services/api.service";
export const getStats = () => {
return ApiService.getStats().then(
(data) => {
return data.data;
}
);
};
export const getRecentScrobbles = (id) => {
return ApiService.getRecentScrobbles(id).then(
(data) => {
return data.data;
}
);
};

View File

@ -1,105 +0,0 @@
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
} from "./types";
import { toast } from 'react-toastify';
import jwt from 'jwt-decode'
import AuthService from "../Services/auth.service";
import eventBus from "./eventBus";
export const register = (username, email, password) => (dispatch) => {
return AuthService.register(username, email, password).then(
(data) => {
if (data.message) {
toast.success('Successfully registered. Please sign in');
dispatch({
type: REGISTER_SUCCESS,
});
return Promise.resolve();
}
toast.error(data.error ? data.error: 'An Unknown Error has occurred')
dispatch({
type: REGISTER_FAIL,
});
return Promise.reject();
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
toast.error(message);
dispatch({
type: REGISTER_FAIL,
});
return Promise.reject();
}
);
};
export const login = (username, password) => (dispatch) => {
return AuthService.login(username, password).then(
(data) => {
if (data.token) {
toast.success('Login Success');
let user = jwt(data.token)
dispatch({
type: LOGIN_SUCCESS,
payload: { jwt: data.token, sub: user.sub, exp: user.exp },
});
return Promise.resolve();
}
toast.error(data.error ? data.error: 'An Unknown Error has occurred')
dispatch({
type: LOGIN_FAIL,
});
return Promise.reject();
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.error) ||
error.message ||
error.toString();
toast.error('Error: ' + message)
dispatch({
type: LOGIN_FAIL,
});
return Promise.reject();
}
);
};
export const logout = (dispatch) => {
// Clear local data
AuthService.logout()
// window.location.pathname("/")
window.location.reload()
// TODO; Clear Redux - ENABLE THIS WHEN I FIGURE OUT HOW 2 DISPATCH
// dispatch({
// type: LOGOUT,
// payload: {},
// });
// // Issue to all listeners to reload
eventBus.dispatch(LOGOUT);
};

View File

@ -1,13 +0,0 @@
const eventBus = {
on(event, callback) {
document.addEventListener(event, (e) => callback(e.detail));
},
dispatch(event, data) {
document.dispatchEvent(new CustomEvent(event, { detail: data }));
},
remove(event, callback) {
document.removeEventListener(event, callback);
},
};
export default eventBus

View File

@ -1,5 +0,0 @@
export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
export const REGISTER_FAIL = "REGISTER_FAIL";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGOUT = "LOGOUT";

74
web/src/Api/index.js Normal file
View File

@ -0,0 +1,74 @@
import axios from 'axios';
import jwt from 'jwt-decode'
import { toast } from 'react-toastify';
function getHeaders() {
// Todo: move this to use Context values instead.
const user = JSON.parse(localStorage.getItem('user'));
if (user && user.jwt) {
return { Authorization: 'Bearer ' + user.jwt };
} else {
return {};
}
}
export const PostLogin = (formValues) => {
// const { setLoading, setUser } = useContext(AuthContext);
// setLoading(true)
return axios.post(process.env.REACT_APP_API_URL + "login", formValues)
.then((response) => {
if (response.data.token) {
let expandedUser = jwt(response.data.token)
let user = {
jwt: response.data.token,
uuid: expandedUser.sub,
exp: expandedUser.exp,
username: expandedUser.username,
}
toast.success('Successfully logged in.');
return user;
} else {
toast.error(response.data.error ? response.data.error: 'An Unknown Error has occurred');
return null
}
})
.catch(() => {
return Promise.resolve();
});
};
export const PostRegister = (formValues) => {
return axios.post(process.env.REACT_APP_API_URL + "register", formValues)
.then((response) => {
if (response.data.message) {
toast.success(response.data.message);
return Promise.resolve();
} else {
toast.error(response.data.error ? response.data.error: 'An Unknown Error has occurred');
return Promise.reject();
}
})
.catch(() => {
return Promise.resolve();
});
};
export const getStats = () => {
return axios.get(process.env.REACT_APP_API_URL + "stats").then(
(data) => {
data.isLoading = false;
return data.data;
}
);
};
export const getRecentScrobbles = (id) => {
return axios.get(process.env.REACT_APP_API_URL + "user/" + id + "/scrobbles", { headers: getHeaders() })
.then((data) => {
return data.data;
});
};

View File

@ -3,47 +3,36 @@ import Home from './Pages/Home';
import About from './Pages/About'; import About from './Pages/About';
import Dashboard from './Pages/Dashboard'; import Dashboard from './Pages/Dashboard';
import Admin from './Pages/Admin';
import Profile from './Pages/Profile'; import Profile from './Pages/Profile';
import Login from './Pages/Login'; import Login from './Pages/Login';
import Settings from './Pages/Settings'; import Settings from './Pages/Settings';
import Register from './Pages/Register'; import Register from './Pages/Register';
import Navigation from './Components/Navigation'; import Navigation from './Components/Navigation';
// import { logout } from './Actions/auth';
import { Route, Switch, withRouter } from 'react-router-dom'; import { Route, Switch, withRouter } from 'react-router-dom';
import { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/css/bootstrap.min.css';
class App extends Component { const App = () => {
constructor(props) { let boolTrue = true
super(props);
this.state = {
true: true,
};
}
render() {
return ( return (
<div> <div>
<Navigation /> <Navigation />
<Switch> <Switch>
<Route exact={this.state.true} path={["/", "/home"]} component={Home} /> <Route exact={boolTrue} path={["/", "/home"]} component={Home} />
<Route path="/about" component={About} /> <Route path="/about" component={About} />
<Route path="/dashboard" component={Dashboard} /> <Route path="/dashboard" component={Dashboard} />
<Route path="/profile" component={Profile} /> <Route path="/profile" component={Profile} />
<Route path="/settings" component={Settings} /> <Route path="/settings" component={Settings} />
<Route path="/admin" component={Admin} />
<Route path="/login" component={Login} /> <Route path="/login" component={Login} />
<Route path="/register" component={Register} /> <Route path="/register" component={Register} />
</Switch> </Switch>
</div> </div>
); );
}
} }
export default withRouter(App); export default withRouter(App);

View File

@ -1,65 +1,45 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import '../App.css'; import '../App.css';
import './HomeBanner.css'; import './HomeBanner.css';
import { getStats } from '../Actions/api'; import { getStats } from '../Api/index';
import ClipLoader from 'react-spinners/ClipLoader' import ClipLoader from 'react-spinners/ClipLoader'
class HomeBanner extends React.Component { const HomeBanner = () => {
constructor(props) { let [bannerData, setBannerData] = useState({});
super(props); let [isLoading, setIsLoading] = useState(true);
this.state = {
isLoading: true,
userCount: 0,
scrobbleCount: 0,
trackCount: 0,
artistCount: 0,
};
}
componentDidMount() { useEffect(() => {
getStats() getStats()
.then((data) => { .then(data => {
this.setState({ setBannerData(data);
isLoading: false, setIsLoading(false);
userCount: data.users, })
scrobbleCount: data.scrobbles, }, [])
trackCount: data.tracks,
artistCount: data.artists,
});
})
.catch(() => {
this.setState({
isLoading: false
});
});
}
render() { return (
return ( <div className="homeBanner">
<div className="homeBanner"> <div className="homeBannerItem">
<div className="homeBannerItem"> {isLoading
{this.state.isLoading ? <ClipLoader color="#6AD7E5" size={34} />
? <ClipLoader color="#6AD7E5" size={36} /> : <span className="homeBannerItemCount">{bannerData.scrobbles}</span>}<br/>Scrobbles
: <span className="homeBannerItemCount">{this.state.scrobbleCount}</span>}<br/>Scrobbles
</div>
<div className="homeBannerItem">
{this.state.isLoading
? <ClipLoader color="#6AD7E5" size={36} />
: <span className="homeBannerItemCount">{this.state.userCount}</span>}<br/>Users
</div>
<div className="homeBannerItem">
{this.state.isLoading
? <ClipLoader color="#6AD7E5" size={36} />
: <span className="homeBannerItemCount">{this.state.trackCount}</span>}<br/>Tracks
</div>
<div className="homeBannerItem">
{this.state.isLoading
? <ClipLoader color="#6AD7E5" size={36} />
: <span className="homeBannerItemCount">{this.state.artistCount}</span>}<br/>Artists
</div>
</div> </div>
); <div className="homeBannerItem">
} {isLoading
? <ClipLoader color="#6AD7E5" size={34} />
: <span className="homeBannerItemCount">{bannerData.users}</span>}<br/>Users
</div>
<div className="homeBannerItem">
{isLoading
? <ClipLoader color="#6AD7E5" size={34} />
: <span className="homeBannerItemCount">{bannerData.tracks}</span>}<br/>Tracks
</div>
<div className="homeBannerItem">
{isLoading
? <ClipLoader color="#6AD7E5" size={34} />
: <span className="homeBannerItemCount">{bannerData.artists}</span>}<br/>Artists
</div>
</div>
);
} }
export default HomeBanner; export default HomeBanner;

View File

@ -1,16 +1,10 @@
import { React, Component } from 'react'; import { React, useState, useContext } from 'react';
import { Navbar, NavbarBrand, Collapse, Nav, NavbarToggler, NavItem } from 'reactstrap'; import { Navbar, NavbarBrand, Collapse, Nav, NavbarToggler, NavItem } from 'reactstrap';
import { Link } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import logo from '../logo.png'; import logo from '../logo.png';
import './Navigation.css'; import './Navigation.css';
import { connect } from 'react-redux';
import { logout } from '../Actions/auth';
import eventBus from "../Actions/eventBus";
import { import AuthContext from '../Contexts/AuthContext';
LOGIN_SUCCESS,
LOGOUT,
} from "../Actions/types";
const menuItems = [ const menuItems = [
'Home', 'Home',
@ -26,197 +20,141 @@ const isMobile = () => {
return (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) return (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent))
}; };
class Navigation extends Component { const Navigation = () => {
constructor(props) { const location = useLocation();
super(props);
this.toggleNavbar = this.toggleNavbar.bind(this);
this.handleLogout = this.handleLogout.bind(this);
// Yeah I know you might not hit home.. but I can't get the // Lovely hack to highlight the current page (:
// path based finder thing working on initial load :sweatsmile: let active = "Home"
this.state = { active: "Home", collapsed: true }; if (location && location.pathname && location.pathname.length > 1) {
active = location.pathname.replace(/\//, "");
} }
componentDidMount() { let activeStyle = { color: '#FFFFFF' };
const { isLoggedIn } = this.props; let { user, Logout } = useContext(AuthContext);
if (isLoggedIn) { let [collapsed, setCollapsed] = useState(true);
this.setState({
isLoggedIn: true,
});
}
eventBus.on(LOGIN_SUCCESS, () => const renderMobileNav = () => {
this.setState({ isLoggedIn: true }) return <Navbar color="dark" dark fixed="top">
); <NavbarBrand className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand>
<NavbarToggler onClick={setCollapsed(!collapsed)} className="mr-2" />
eventBus.on(LOGOUT, () => <Collapse isOpen={!collapsed} navbar>
this.setState({ isLoggedIn: false }) {user ?
); <Nav className="navLinkLoginMobile" navbar>
} {loggedInMenuItems.map(menuItem =>
<NavItem>
componentWillUnmount() { <Link
eventBus.remove(LOGIN_SUCCESS); key={menuItem}
} className="navLinkMobile"
style={active === menuItem ? activeStyle : {}}
_handleClick(menuItem) { to={menuItem}
this.setState({ active: menuItem, collapsed: !this.state.collapsed }); >{menuItem}</Link>
} </NavItem>
)}
handleLogout() { <Link
this.dispatch(logout()); to="/profile"
} style={active === "profile" ? activeStyle : {}}
className="navLinkMobile"
toggleNavbar() { >Profile</Link>
this.setState({ collapsed: !this.state.collapsed }); <Link to="/" className="navLink" onClick={Logout}>Logout</Link>
} </Nav>
: <Nav className="navLinkLoginMobile" navbar>
// This is a real mess. TO CLEAN UP. {menuItems.map(menuItem =>
render() { <NavItem>
const activeStyle = { color: '#FFFFFF' }; <Link
key={menuItem}
const renderMobileNav = () => { className="navLinkMobile"
return <Navbar color="dark" dark fixed="top"> style={active === menuItem ? activeStyle : {}}
<NavbarBrand className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand> to={menuItem === "Home" ? "/" : menuItem}
<NavbarToggler onClick={this.toggleNavbar} className="mr-2" /> >
<Collapse isOpen={!this.state.collapsed} navbar> {menuItem}
{this.state.isLoggedIn ? </Link>
<Nav className="navLinkLoginMobile" navbar> </NavItem>
{loggedInMenuItems.map(menuItem => )}
<NavItem> <NavItem>
<Link <Link
key={menuItem} to="/Login"
style={active === "Login" ? activeStyle : {}}
className="navLinkMobile" className="navLinkMobile"
style={this.state.active === menuItem ? activeStyle : {}} >Login</Link>
onClick={this._handleClick.bind(this, menuItem)}
to={menuItem}
>{menuItem}</Link>
</NavItem> </NavItem>
)} <NavItem>
<Link
to="/Register"
className="navLinkMobile"
style={active === "Register" ? activeStyle : {}}
>Register</Link>
</NavItem>
</Nav>
}
</Collapse>
</Navbar>
}
const renderDesktopNav = () => {
return <Navbar color="dark" dark fixed="top">
<NavbarBrand className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand>
{user ?
<div>
{loggedInMenuItems.map(menuItem =>
<Link
key={menuItem}
className="navLink"
style={active === menuItem ? activeStyle : {}}
to={menuItem}
>
{menuItem}
</Link>
)}
</div>
: <div>
{menuItems.map(menuItem =>
<Link
key={menuItem}
className="navLink"
style={active === menuItem ? activeStyle : {}}
to={menuItem === "Home" ? "/" : menuItem}
>
{menuItem}
</Link>
)}
</div>
}
{user ?
<div className="navLinkLogin">
<Link <Link
to="/profile" to="/profile"
style={this.state.active === "profile" ? activeStyle : {}} style={active === "profile" ? activeStyle : {}}
onClick={this._handleClick.bind(this, "profile")}
className="navLinkMobile"
>Profile</Link>
<Link to="/" className="navLink" onClick={this.handleLogout}>Logout</Link>
</Nav>
: <Nav className="navLinkLoginMobile" navbar>
{menuItems.map(menuItem =>
<NavItem>
<Link
key={menuItem}
className="navLinkMobile"
style={this.state.active === menuItem ? activeStyle : {}}
onClick={this._handleClick.bind(this, menuItem)}
to={menuItem === "Home" ? "/" : menuItem}
>
{menuItem}
</Link>
</NavItem>
)}
<NavItem>
<Link
to="/login"
style={this.state.active === "login" ? activeStyle : {}}
onClick={this._handleClick.bind(this, "login")}
className="navLinkMobile"
>Login</Link>
</NavItem>
<NavItem>
<Link
to="/register"
className="navLinkMobile"
style={this.state.active === "register" ? activeStyle : {}}
onClick={this._handleClick.bind(this, "register")}
history={this.props.history}
>Register</Link>
</NavItem>
</Nav>
}
</Collapse>
</Navbar>
}
const renderDesktopNav = () => {
return <Navbar color="dark" dark fixed="top">
<NavbarBrand className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand>
{this.state.isLoggedIn ?
<div>
{loggedInMenuItems.map(menuItem =>
<Link
key={menuItem}
className="navLink" className="navLink"
style={this.state.active === menuItem ? activeStyle : {}} >{user.username}</Link>
onClick={this._handleClick.bind(this, menuItem)} <Link to="/" className="navLink" onClick={Logout}>Logout</Link>
to={menuItem} </div>
> :
{menuItem} <div className="navLinkLogin">
</Link> <Link
)} to="/login"
</div> style={active === "login" ? activeStyle : {}}
: <div> className="navLink"
{menuItems.map(menuItem => >Login</Link>
<Link <Link
key={menuItem} to="/register"
className="navLink" className="navLink"
style={this.state.active === menuItem ? activeStyle : {}} style={active === "register" ? activeStyle : {}}
onClick={this._handleClick.bind(this, menuItem)} >Register</Link>
to={menuItem === "Home" ? "/" : menuItem} </div>
>
{menuItem}
</Link>
)}
</div>
}
{this.state.isLoggedIn ?
<div className="navLinkLogin">
<Link
to="/profile"
style={this.state.active === "profile" ? activeStyle : {}}
onClick={this._handleClick.bind(this, "profile")}
className="navLink"
>Profile</Link>
<Link to="/" className="navLink" onClick={this.handleLogout}>Logout</Link>
</div>
:
<div className="navLinkLogin">
<Link
to="/login"
style={this.state.active === "login" ? activeStyle : {}}
onClick={this._handleClick.bind(this, "login")}
className="navLink"
>Login</Link>
<Link
to="/register"
className="navLink"
style={this.state.active === "register" ? activeStyle : {}}
onClick={this._handleClick.bind(this, "register")}
history={this.props.history}
>Register</Link>
</div>
} }
</Navbar> </Navbar>
}
return (
<div>
{
isMobile()
? renderMobileNav()
: renderDesktopNav()
}
</div>
);
} }
return (
<div>
{
isMobile()
? renderMobileNav()
: renderDesktopNav()
}
</div>
);
} }
function mapStateToProps(state) { export default Navigation;
const { isLoggedIn } = state.auth;
return {
isLoggedIn
};
}
export default connect(mapStateToProps)(Navigation);

View File

@ -1,15 +1,6 @@
import React from "react"; import React from "react";
class ScrobbleTable extends React.Component { const ScrobbleTable = (props) => {
constructor(props) {
super(props);
this.state = {
data: this.props.data,
};
}
render() {
return ( return (
<div> <div>
<table border={1} cellPadding={5}> <table border={1} cellPadding={5}>
@ -23,8 +14,8 @@ class ScrobbleTable extends React.Component {
</thead> </thead>
<tbody> <tbody>
{ {
this.state.data && this.state.data.items && props.data && props.data.items &&
this.state.data.items.map(function (element) { props.data.items.map(function (element) {
return <tr> return <tr>
<td>{element.time}</td> <td>{element.time}</td>
<td>{element.track}</td> <td>{element.track}</td>
@ -37,7 +28,6 @@ class ScrobbleTable extends React.Component {
</table> </table>
</div> </div>
); );
}
} }
export default ScrobbleTable; export default ScrobbleTable;

View File

@ -0,0 +1,5 @@
import React from 'react';
const AuthContext = React.createContext();
export default AuthContext;

View File

@ -0,0 +1,59 @@
import React, { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import AuthContext from './AuthContext';
import { PostLogin, PostRegister } from '../Api/index';
const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState();
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true)
const user = JSON.parse(localStorage.getItem('user'));
if (user && user.jwt) {
setUser(user)
}
setLoading(false)
}, []);
const Login = (formValues) => {
setLoading(true);
PostLogin(formValues).then(user => {
if (user) {
setUser(user);
localStorage.setItem('user', JSON.stringify(user));
}
setLoading(false);
})
}
const Register = (formValues) => {
setLoading(true);
return PostRegister(formValues).then(response => {
// Do stuff here?
setLoading(false);
});
};
const Logout = () => {
localStorage.removeItem("user");
setUser(null)
toast.success('Successfully logged out.');
};
return (
<AuthContext.Provider
value={{
Logout,
Login,
Register,
loading,
user,
}}
>
{children}
</AuthContext.Provider>
);
};
export default AuthContextProvider;

View File

@ -1,7 +1,7 @@
import '../App.css'; import '../App.css';
import './About.css'; import './About.css';
function About() { const About = () => {
return ( return (
<div className="pageWrapper"> <div className="pageWrapper">
<h1> <h1>
@ -12,7 +12,7 @@ function About() {
Used to track your listening history and build a profile to discover new music. Used to track your listening history and build a profile to discover new music.
</p> </p>
<a <a
className="aboutBodyw" className="aboutBody"
href="https://gitlab.com/idanoo/go-scrobble" href="https://gitlab.com/idanoo/go-scrobble"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"

View File

@ -1,45 +0,0 @@
import React, { Component } from "react";
import UserService from "../Services/user.service";
class Admin extends Component {
constructor(props) {
super(props);
this.state = {
content: ""
};
}
componentDidMount() {
UserService.getAdminBoard().then(
response => {
this.setState({
content: response.data
});
},
error => {
this.setState({
content:
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString()
});
}
);
}
render() {
return (
<div className="container">
<header className="jumbotron">
<h3>{this.state.content}</h3>
</header>
</div>
);
}
}
export default Admin;

View File

@ -1,69 +1,44 @@
import React from 'react'; import React, { useState, useEffect, useContext } from 'react';
import '../App.css'; import '../App.css';
import './Dashboard.css'; import './Dashboard.css';
import { connect } from 'react-redux'; import { useHistory } from "react-router";
import { getRecentScrobbles } from '../Actions/api'; import { getRecentScrobbles } from '../Api/index';
import ScaleLoader from 'react-spinners/ScaleLoader'; import ScaleLoader from 'react-spinners/ScaleLoader';
import ScrobbleTable from "../Components/ScrobbleTable"; import ScrobbleTable from "../Components/ScrobbleTable";
import AuthContext from '../Contexts/AuthContext';
class Dashboard extends React.Component { const Dashboard = () => {
constructor(props) { const history = useHistory();
super(props); let { user } = useContext(AuthContext);
this.state = { let [isLoading, setIsLoading] = useState(true);
isLoading: true, let [dashboardData, setDashboardData] = useState({});
scrobbleData: [],
uuid: null, if (!user) {
}; history.push("/login");
} }
componentDidMount() { useEffect(() => {
const { history, uuid } = this.props; if (!user) {
const isLoggedIn = this.props.isLoggedIn; return
if (!isLoggedIn) {
history.push("/login")
} }
getRecentScrobbles(user.uuid)
.then(data => {
setDashboardData(data);
setIsLoading(false);
})
}, [user])
getRecentScrobbles(uuid) return (
.then((data) => { <div className="pageWrapper">
this.setState({ <h1>
isLoading: false, Dashboard!
data: data </h1>
}); {isLoading
}) ? <ScaleLoader color="#FFF" size={60} />
.catch(() => { : <ScrobbleTable data={dashboardData} />
this.setState({ }
isLoading: false </div>
}); );
});
}
render() {
return (
<div className="pageWrapper">
<h1>
Dashboard!
</h1>
{this.state.isLoading
? <ScaleLoader color="#FFF" size={60} />
: <ScrobbleTable data={this.state.data} />
}
</div>
);
}
} }
function mapStateToProps(state) { export default Dashboard;
const { isLoggedIn } = state.auth;
let uuid = null;
if (isLoggedIn) {
uuid = state.auth.user.uuid
}
return {
isLoggedIn,
uuid,
};
}
export default connect(mapStateToProps)(Dashboard);

View File

@ -4,16 +4,14 @@ import './Home.css';
import HomeBanner from '../Components/HomeBanner'; import HomeBanner from '../Components/HomeBanner';
import React from 'react'; import React from 'react';
class Home extends React.Component { const Home = () => {
render() { return (
return (
<div className="pageWrapper"> <div className="pageWrapper">
<img src={logo} className="App-logo" alt="logo" /> <img src={logo} className="App-logo" alt="logo" />
<p className="homeText">Go-Scrobble is an open source music scrobbling service written in Go and React.</p> <p className="homeText">Go-Scrobble is an open source music scrobbling service written in Go and React.</p>
<HomeBanner /> <HomeBanner />
</div> </div>
); );
}
} }
export default Home; export default Home;

View File

@ -1,102 +1,63 @@
import React from 'react'; import React, { useContext } from 'react';
import '../App.css'; import '../App.css';
import './Login.css'; import './Login.css';
import { Button } from 'reactstrap'; import { Button } from 'reactstrap';
import { Formik, Form, Field } from 'formik'; import { Formik, Form, Field } from 'formik';
import ScaleLoader from 'react-spinners/ScaleLoader'; import ScaleLoader from 'react-spinners/ScaleLoader';
import { connect } from 'react-redux'; import AuthContext from '../Contexts/AuthContext';
import { login } from '../Actions/auth'; import { useHistory } from "react-router";
import eventBus from "../Actions/eventBus";
import { LOGIN_SUCCESS } from '../Actions/types';
class Login extends React.Component { const Login = () => {
constructor(props) { const history = useHistory();
super(props); let boolTrue = true;
this.state = {username: '', password: '', loading: false}; let { Login, loading, user } = useContext(AuthContext);
if (user) {
history.push("/dashboard");
} }
componentDidMount() { return (
const { history, isLoggedIn } = this.props; <div className="pageWrapper">
<h1>
if (isLoggedIn) { Login
history.push("/dashboard") </h1>
} <div className="loginBody">
} <Formik
initialValues={{ username: '', password: '' }}
handleLogin(values) { onSubmit={values => Login(values)}
this.setState({loading: true}); >
<Form>
const { dispatch, history } = this.props; <label>
Email / Username<br/>
dispatch(login(values.username, values.password)) <Field
.then(() => { name="username"
this.setState({ type="text"
loading: false, required={boolTrue}
isLoggedIn: true className="loginFields"
}); />
</label>
eventBus.dispatch(LOGIN_SUCCESS, { isLoggedIn: true }); <br/>
history.push("/dashboard"); <label>
}) Password<br/>
.catch(() => { <Field
this.setState({ name="password"
loading: false type="password"
}); required={boolTrue}
}); className="loginFields"
} />
</label>
render() { <br/><br/>
let trueBool = true; <Button
return ( color="primary"
<div className="pageWrapper"> type="submit"
<h1> className="loginButton"
Login disabled={loading}
</h1> >{loading ? <ScaleLoader color="#FFF" size={35} /> : "Login"}</Button>
<div className="loginBody"> </Form>
<Formik </Formik>
initialValues={{ username: '', password: '' }}
onSubmit={async values => this.handleLogin(values)}
>
<Form>
<label>
Email / Username<br/>
<Field
name="username"
type="text"
required={trueBool}
className="loginFields"
/>
</label>
<br/>
<label>
Password<br/>
<Field
name="password"
type="password"
required={trueBool}
className="loginFields"
/>
</label>
<br/><br/>
<Button
color="primary"
type="submit"
className="loginButton"
disabled={this.state.loading}
>{this.state.loading ? <ScaleLoader color="#FFF" size={35} /> : "Login"}</Button>
</Form>
</Formik>
</div>
</div> </div>
); </div>
} );
} }
function mapStateToProps(state) { export default Login;
const { isLoggedIn } = state.auth;
return {
isLoggedIn
};
}
export default connect(mapStateToProps)(Login);

View File

@ -1,33 +1,25 @@
import React from 'react'; import React, { useContext } from 'react';
import '../App.css'; import '../App.css';
import './Dashboard.css'; import './Dashboard.css';
import { connect } from 'react-redux'; import { useHistory } from "react-router";
import AuthContext from '../Contexts/AuthContext';
class Profile extends React.Component { const Profile = () => {
componentDidMount() { const history = useHistory();
const { history, isLoggedIn } = this.props; const { user } = useContext(AuthContext);
if (!isLoggedIn) { if (!user) {
history.push("/login") history.push("/login");
}
} }
render() { return (
return ( <div className="pageWrapper">
<div className="pageWrapper"> <h1>
<h1> Welcome {user.username}!
Hai User </h1>
</h1> </div>
</div> );
);
}
} }
function mapStateToProps(state) { export default Profile;
const { isLoggedIn } = state.auth;
return {
isLoggedIn,
};
}
export default connect(mapStateToProps)(Profile);

View File

@ -1,125 +1,90 @@
import React from 'react'; import React, { useContext } from 'react';
import '../App.css'; import '../App.css';
import './Register.css'; import './Register.css';
import { Button } from 'reactstrap'; import { Button } from 'reactstrap';
import ScaleLoader from "react-spinners/ScaleLoader"; import ScaleLoader from "react-spinners/ScaleLoader";
import { register } from '../Actions/auth'; import AuthContext from '../Contexts/AuthContext';
import { Formik, Field, Form } from 'formik'; import { Formik, Field, Form } from 'formik';
import { connect } from 'react-redux'; import { useHistory } from "react-router";
class Register extends React.Component { const Register = () => {
constructor(props) { const history = useHistory();
super(props); let boolTrue = true;
this.state = {username: '', email: '', password: '', passwordconfirm: '', loading: false}; let { Register, user, loading } = useContext(AuthContext);
if (user) {
history.push("/dashboard");
} }
componentDidMount() { return (
const { history, isLoggedIn } = this.props; <div className="pageWrapper">
{
if (isLoggedIn) { // TODO: Move to DB:config REGISTRATION_DISABLED=1|0 :upsidedownsmile:
history.push("/dashboard") process.env.REACT_APP_REGISTRATION_DISABLED === "true" ?
} <p>Registration is temporarily disabled. Please try again soon!</p>
} :
<div>
handleRegister(values) { <h1>
console.log(values) Register
this.setState({loading: true}); </h1>
<div className="registerBody">
const { dispatch, history } = this.props; <Formik
initialValues={{ username: '', email: '', password: '', passwordconfirm: '' }}
dispatch(register(values.username, values.email, values.password)) onSubmit={async values => Register(values)}
.then(() => { >
this.setState({ <Form>
loading: false, <label>
}); Username*<br/>
history.push("/login"); <Field
}) name="username"
.catch(() => { type="text"
this.setState({ required={boolTrue}
loading: false className="registerFields"
}); />
}); </label>
} <br/>
<label>
render() { Email<br/>
let trueBool = true; <Field
return ( name="email"
<div className="pageWrapper"> type="email"
{ className="registerFields"
// TODO: Move to DB:config REGISTRATION_DISABLED=1|0 />
process.env.REACT_APP_REGISTRATION_DISABLED === "true" ? </label>
<p>Registration is temporarily disabled. Please try again soon!</p> <br/>
: <label>
<div> Password*<br/>
<h1> <Field
Register name="password"
</h1> type="password"
<div className="registerBody"> required={boolTrue}
<Formik className="registerFields"
initialValues={{ username: '', email: '', password: '', passwordconfirm: '' }} />
onSubmit={async values => this.handleRegister(values)} </label>
> <br/>
<Form> <label>
<label> Confirm Password*<br/>
Username*<br/> <Field
<Field name="passwordconfirm"
name="username" type="password"
type="text" required={boolTrue}
required={trueBool} className="registerFields"
className="registerFields" />
/> </label>
</label> <br/><br/>
<br/> <Button
<label> color="primary"
Email<br/> type="submit"
<Field className="registerButton"
name="email" disabled={loading}
type="email" >{loading ? <ScaleLoader color="#FFF" size={35} /> : "Register"}</Button>
className="registerFields" </Form>
/> </Formik>
</label>
<br/>
<label>
Password*<br/>
<Field
name="password"
type="password"
required={trueBool}
className="registerFields"
/>
</label>
<br/>
<label>
Confirm Password*<br/>
<Field
name="passwordconfirm"
type="password"
required={trueBool}
className="registerFields"
/>
</label>
<br/><br/>
<Button
color="primary"
type="submit"
className="registerButton"
disabled={this.state.loading}
>{this.state.loading ? <ScaleLoader color="#FFF" size={35} /> : "Register"}</Button>
</Form>
</Formik>
</div>
</div> </div>
} </div>
</div> }
); </div>
} );
} }
function mapStateToProps(state) { export default Register;
const { isLoggedIn } = state.auth;
return {
isLoggedIn
};
}
export default connect(mapStateToProps)(Register);

View File

@ -2,35 +2,19 @@ import React from 'react';
import '../App.css'; import '../App.css';
import './Settings.css'; import './Settings.css';
import { useToasts } from 'react-toast-notifications'; const Settings = () => {
return (
function withToast(Component) { <div className="pageWrapper">
return function WrappedComponent(props) { <h1>
const toastFuncs = useToasts() Settings
return <Component {...props} {...toastFuncs} />; </h1>
} <div className="loginBody">
} <p>
All the settings
class Settings extends React.Component { </p>
constructor(props) {
super(props);
this.state = {username: '', password: '', loading: false};
}
render() {
return (
<div className="pageWrapper">
<h1>
Settings
</h1>
<div className="loginBody">
<p>
All the settings
</p>
</div>
</div> </div>
); </div>
} );
} }
export default withToast(Settings); export default Settings;

View File

@ -1,53 +0,0 @@
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
} from "../Actions/types";
const user = JSON.parse(localStorage.getItem('user'));
const initialState = user
? { isLoggedIn: true, user: user }
: { isLoggedIn: false, user: null };
export default function authReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case REGISTER_SUCCESS:
return {
...state,
isLoggedIn: false,
};
case REGISTER_FAIL:
return {
...state,
isLoggedIn: false,
};
case LOGIN_SUCCESS:
return {
...state,
isLoggedIn: true,
user: {
jwt: payload.jwt,
uuid: payload.sub,
exp: payload.exp,
}
};
case LOGIN_FAIL:
return {
...state,
isLoggedIn: false,
};
case LOGOUT:
return {
...state,
isLoggedIn: false,
user: null,
};
default:
return state;
}
}

View File

@ -1,6 +0,0 @@
import { combineReducers } from "redux";
import auth from "./auth";
export default combineReducers({
auth,
});

View File

@ -1,14 +0,0 @@
import axios from "axios";
import authHeader from '../Services/auth-header';
class ApiService {
async getStats() {
return axios.get(process.env.REACT_APP_API_URL + "stats");
}
async getRecentScrobbles(id) {
return axios.get(process.env.REACT_APP_API_URL + "user/" + id + "/scrobbles", { headers: authHeader() });
}
}
export default new ApiService();

View File

@ -1,9 +0,0 @@
export default function authHeader() {
const user = JSON.parse(localStorage.getItem('user'));
if (user && user.jwt) {
return { Authorization: 'Bearer ' + user.jwt };
} else {
return {};
}
}

View File

@ -1,41 +0,0 @@
import axios from "axios";
import jwt from 'jwt-decode' // import dependency
class AuthService {
async login(username, password) {
return axios
.post(process.env.REACT_APP_API_URL + "login", { username, password })
.then((response) => {
if (response.data.token) {
let expandedUser = jwt(response.data.token)
let user = {
jwt: response.data.token,
uuid: expandedUser.sub,
exp: expandedUser.exp,
}
localStorage.setItem('user', JSON.stringify(user))
}
return response.data;
});
}
async logout() {
localStorage.removeItem("user");
}
async register(username, email, password) {
return axios
.post(process.env.REACT_APP_API_URL + "register", {
username,
email,
password,
})
.then((response) => {
console.log(response)
return response.data;
});
}
}
export default new AuthService();

View File

@ -1,18 +0,0 @@
import axios from 'axios';
import authHeader from './auth-header';
class UserService {
getPublicContent() {
return axios.get(process.env.REACT_APP_API_URL + 'all');
}
getUserBoard() {
return axios.get(process.env.REACT_APP_API_URL + 'user', { headers: authHeader() });
}
getAdminBoard() {
return axios.get(process.env.REACT_APP_API_URL + 'admin', { headers: authHeader() });
}
}
export default new UserService();

View File

@ -3,13 +3,14 @@ import ReactDOM from 'react-dom';
import './index.css'; import './index.css';
import App from './App'; import App from './App';
import { HashRouter } from 'react-router-dom'; import { HashRouter } from 'react-router-dom';
import { ToastContainer } from 'react-toastify'; import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.min.css' import 'react-toastify/dist/ReactToastify.min.css';
import { Provider } from 'react-redux';
import store from "./store"; import AuthContextProvider from './Contexts/AuthContextProvider';
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <AuthContextProvider>
<HashRouter> <HashRouter>
<ToastContainer <ToastContainer
position="bottom-right" position="bottom-right"
@ -24,6 +25,6 @@ ReactDOM.render(
/> />
<App /> <App />
</HashRouter> </HashRouter>
</Provider>, </AuthContextProvider>,
document.getElementById('root') document.getElementById('root')
); );

View File

@ -1,13 +0,0 @@
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import rootReducer from "./Reducers";
const middleware = [thunk];
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;