diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0a6276eb..10d39ba9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,11 +3,12 @@ stages: - bundle variables: - VERSION: 0.0.6 + VERSION: 0.0.7 build-go: image: golang:1.16.2 stage: build + only: master script: - go build -o goscrobble cmd/go-scrobble/*.go artifacts: @@ -21,6 +22,7 @@ build-go: build-react: image: node:15.12.0 stage: build + only: master script: - cd web - npm install @@ -33,6 +35,7 @@ build-react: bundle: image: bash:latest stage: bundle + only: master variables: GIT_STRATEGY: none before_script: diff --git a/docs/changelog.md b/docs/changelog.md index 19a35080..d92978d8 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,8 @@ +# 0.0.7 +- Switch redux -> Context +- Remove excess packages + + # 0.0.6 - Fix hitting dashboard when logged out - Clean up app.js diff --git a/internal/goscrobble/user.go b/internal/goscrobble/user.go index 2b3fff66..c1626325 100644 --- a/internal/goscrobble/user.go +++ b/internal/goscrobble/user.go @@ -61,10 +61,8 @@ func createUser(req *RegisterRequest, ip net.IP) error { // Check username is valid if !isUsernameValid(req.Username) { - log.Println("user is invalid") return errors.New("Username contains invalid characters") } - log.Println("user is valid") // If set an email.. validate it! if req.Email != "" { diff --git a/web/package-lock.json b/web/package-lock.json index a86fda08..de4226a5 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -19,21 +19,13 @@ "react": "^17.0.2", "react-bootstrap": "^1.5.2", "react-cookie": "^4.0.3", - "react-data-grid": "*", "react-dom": "^17.0.2", "react-redux": "^7.2.3", "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", "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", - "reactstrap": "^8.9.0", - "redux": "^4.0.5", - "redux-persist": "^6.0.0", - "redux-thunk": "^2.3.0", - "web-vitals": "^1.1.1" + "reactstrap": "^8.9.0" }, "devDependencies": { "redux-devtools-extension": "^2.13.9", @@ -16328,18 +16320,6 @@ "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": { "version": "11.0.4", "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" } }, - "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": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.3.tgz", @@ -17011,14 +16958,6 @@ "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": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", @@ -20691,11 +20630,6 @@ "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": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", @@ -35007,14 +34941,6 @@ "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": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", @@ -35331,27 +35257,6 @@ "@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": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-7.0.3.tgz", @@ -35543,12 +35448,6 @@ "dev": true, "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": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", @@ -38488,11 +38387,6 @@ "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": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", diff --git a/web/package.json b/web/package.json index 745e2036..44605bb0 100644 --- a/web/package.json +++ b/web/package.json @@ -14,21 +14,13 @@ "react": "^17.0.2", "react-bootstrap": "^1.5.2", "react-cookie": "^4.0.3", - "react-data-grid": "*", "react-dom": "^17.0.2", "react-redux": "^7.2.3", "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", "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", - "reactstrap": "^8.9.0", - "redux": "^4.0.5", - "redux-persist": "^6.0.0", - "redux-thunk": "^2.3.0", - "web-vitals": "^1.1.1" + "reactstrap": "^8.9.0" }, "scripts": { "start": "react-scripts start", diff --git a/web/src/Actions/api.js b/web/src/Actions/api.js deleted file mode 100644 index 57e591d2..00000000 --- a/web/src/Actions/api.js +++ /dev/null @@ -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; - } - ); -}; - diff --git a/web/src/Actions/auth.js b/web/src/Actions/auth.js deleted file mode 100644 index 8f37d2f4..00000000 --- a/web/src/Actions/auth.js +++ /dev/null @@ -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); - }; diff --git a/web/src/Actions/eventBus.js b/web/src/Actions/eventBus.js deleted file mode 100644 index 17bfcd26..00000000 --- a/web/src/Actions/eventBus.js +++ /dev/null @@ -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 \ No newline at end of file diff --git a/web/src/Actions/types.js b/web/src/Actions/types.js deleted file mode 100644 index 98fda989..00000000 --- a/web/src/Actions/types.js +++ /dev/null @@ -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"; diff --git a/web/src/Api/index.js b/web/src/Api/index.js new file mode 100644 index 00000000..ba263584 --- /dev/null +++ b/web/src/Api/index.js @@ -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; + }); +}; diff --git a/web/src/App.js b/web/src/App.js index 02c1c903..7c440fec 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -3,47 +3,36 @@ import Home from './Pages/Home'; import About from './Pages/About'; import Dashboard from './Pages/Dashboard'; -import Admin from './Pages/Admin'; import Profile from './Pages/Profile'; import Login from './Pages/Login'; import Settings from './Pages/Settings'; import Register from './Pages/Register'; import Navigation from './Components/Navigation'; -// import { logout } from './Actions/auth'; import { Route, Switch, withRouter } from 'react-router-dom'; -import { Component } from 'react'; import 'bootstrap/dist/css/bootstrap.min.css'; -class App extends Component { - constructor(props) { - super(props); - this.state = { - true: true, - }; - } +const App = () => { + let boolTrue = true - render() { return (
- + - -
); - } } + export default withRouter(App); \ No newline at end of file diff --git a/web/src/Components/HomeBanner.js b/web/src/Components/HomeBanner.js index c4a4cdf4..66332e4a 100644 --- a/web/src/Components/HomeBanner.js +++ b/web/src/Components/HomeBanner.js @@ -1,65 +1,45 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import '../App.css'; import './HomeBanner.css'; -import { getStats } from '../Actions/api'; +import { getStats } from '../Api/index'; import ClipLoader from 'react-spinners/ClipLoader' -class HomeBanner extends React.Component { - constructor(props) { - super(props); - this.state = { - isLoading: true, - userCount: 0, - scrobbleCount: 0, - trackCount: 0, - artistCount: 0, - }; - } +const HomeBanner = () => { + let [bannerData, setBannerData] = useState({}); + let [isLoading, setIsLoading] = useState(true); - componentDidMount() { + useEffect(() => { getStats() - .then((data) => { - this.setState({ - isLoading: false, - userCount: data.users, - scrobbleCount: data.scrobbles, - trackCount: data.tracks, - artistCount: data.artists, - }); - }) - .catch(() => { - this.setState({ - isLoading: false - }); - }); - } + .then(data => { + setBannerData(data); + setIsLoading(false); + }) + }, []) - render() { - return ( -
-
- {this.state.isLoading - ? - : {this.state.scrobbleCount}}
Scrobbles -
-
- {this.state.isLoading - ? - : {this.state.userCount}}
Users -
-
- {this.state.isLoading - ? - : {this.state.trackCount}}
Tracks -
-
- {this.state.isLoading - ? - : {this.state.artistCount}}
Artists -
+ return ( +
+
+ {isLoading + ? + : {bannerData.scrobbles}}
Scrobbles
- ); - } +
+ {isLoading + ? + : {bannerData.users}}
Users +
+
+ {isLoading + ? + : {bannerData.tracks}}
Tracks +
+
+ {isLoading + ? + : {bannerData.artists}}
Artists +
+
+ ); } export default HomeBanner; diff --git a/web/src/Components/Navigation.js b/web/src/Components/Navigation.js index f8f93239..5c3ea871 100644 --- a/web/src/Components/Navigation.js +++ b/web/src/Components/Navigation.js @@ -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 { Link } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import logo from '../logo.png'; import './Navigation.css'; -import { connect } from 'react-redux'; -import { logout } from '../Actions/auth'; -import eventBus from "../Actions/eventBus"; -import { - LOGIN_SUCCESS, - LOGOUT, -} from "../Actions/types"; +import AuthContext from '../Contexts/AuthContext'; const menuItems = [ 'Home', @@ -26,197 +20,141 @@ const isMobile = () => { return (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) }; -class Navigation extends Component { - constructor(props) { - super(props); - this.toggleNavbar = this.toggleNavbar.bind(this); - this.handleLogout = this.handleLogout.bind(this); +const Navigation = () => { + const location = useLocation(); - // Yeah I know you might not hit home.. but I can't get the - // path based finder thing working on initial load :sweatsmile: - this.state = { active: "Home", collapsed: true }; + // Lovely hack to highlight the current page (: + let active = "Home" + if (location && location.pathname && location.pathname.length > 1) { + active = location.pathname.replace(/\//, ""); } - componentDidMount() { - const { isLoggedIn } = this.props; - if (isLoggedIn) { - this.setState({ - isLoggedIn: true, - }); - } + let activeStyle = { color: '#FFFFFF' }; + let { user, Logout } = useContext(AuthContext); + let [collapsed, setCollapsed] = useState(true); - eventBus.on(LOGIN_SUCCESS, () => - this.setState({ isLoggedIn: true }) - ); - - eventBus.on(LOGOUT, () => - this.setState({ isLoggedIn: false }) - ); - } - - componentWillUnmount() { - eventBus.remove(LOGIN_SUCCESS); - } - - _handleClick(menuItem) { - this.setState({ active: menuItem, collapsed: !this.state.collapsed }); - } - - handleLogout() { - this.dispatch(logout()); - } - - toggleNavbar() { - this.setState({ collapsed: !this.state.collapsed }); - } - - // This is a real mess. TO CLEAN UP. - render() { - const activeStyle = { color: '#FFFFFF' }; - - const renderMobileNav = () => { - return - logo GoScrobble - - - {this.state.isLoggedIn ? - - : - } - - - } - - const renderDesktopNav = () => { - return - logo GoScrobble - {this.state.isLoggedIn ? -
- {loggedInMenuItems.map(menuItem => - - {menuItem} - - )} -
- :
- {menuItems.map(menuItem => - - {menuItem} - - )} -
- } - {this.state.isLoggedIn ? -
- Profile - Logout -
- : -
- Login - Register -
+ >{user.username} + Logout +
+ : +
+ Login + Register +
- } - - } - - return ( -
- { - isMobile() - ? renderMobileNav() - : renderDesktopNav() - } -
- ); + } + } + + return ( +
+ { + isMobile() + ? renderMobileNav() + : renderDesktopNav() + } +
+ ); } -function mapStateToProps(state) { - const { isLoggedIn } = state.auth; - - return { - isLoggedIn - }; -} - -export default connect(mapStateToProps)(Navigation); \ No newline at end of file +export default Navigation; \ No newline at end of file diff --git a/web/src/Components/ScrobbleTable.js b/web/src/Components/ScrobbleTable.js index c15e6e93..57507072 100644 --- a/web/src/Components/ScrobbleTable.js +++ b/web/src/Components/ScrobbleTable.js @@ -1,15 +1,6 @@ import React from "react"; -class ScrobbleTable extends React.Component { - constructor(props) { - super(props); - - this.state = { - data: this.props.data, - }; - } - - render() { +const ScrobbleTable = (props) => { return (
@@ -23,8 +14,8 @@ class ScrobbleTable extends React.Component { { - this.state.data && this.state.data.items && - this.state.data.items.map(function (element) { + props.data && props.data.items && + props.data.items.map(function (element) { return @@ -37,7 +28,6 @@ class ScrobbleTable extends React.Component {
{element.time} {element.track}
); - } } export default ScrobbleTable; \ No newline at end of file diff --git a/web/src/Contexts/AuthContext.js b/web/src/Contexts/AuthContext.js new file mode 100644 index 00000000..c5b074de --- /dev/null +++ b/web/src/Contexts/AuthContext.js @@ -0,0 +1,5 @@ +import React from 'react'; + +const AuthContext = React.createContext(); + +export default AuthContext; \ No newline at end of file diff --git a/web/src/Contexts/AuthContextProvider.js b/web/src/Contexts/AuthContextProvider.js new file mode 100644 index 00000000..0b0907b1 --- /dev/null +++ b/web/src/Contexts/AuthContextProvider.js @@ -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 ( + + {children} + + ); +}; + +export default AuthContextProvider; \ No newline at end of file diff --git a/web/src/Pages/About.js b/web/src/Pages/About.js index 9e800c3a..cfd74942 100644 --- a/web/src/Pages/About.js +++ b/web/src/Pages/About.js @@ -1,7 +1,7 @@ import '../App.css'; import './About.css'; -function About() { +const About = () => { return (

@@ -12,7 +12,7 @@ function About() { Used to track your listening history and build a profile to discover new music.

{ - this.setState({ - content: response.data - }); - }, - error => { - this.setState({ - content: - (error.response && - error.response.data && - error.response.data.message) || - error.message || - error.toString() - }); - } - ); - } - - render() { - return ( -
-
-

{this.state.content}

-
-
- ); - } -} - -export default Admin; \ No newline at end of file diff --git a/web/src/Pages/Dashboard.js b/web/src/Pages/Dashboard.js index 20c0a08a..a714005d 100644 --- a/web/src/Pages/Dashboard.js +++ b/web/src/Pages/Dashboard.js @@ -1,69 +1,44 @@ -import React from 'react'; +import React, { useState, useEffect, useContext } from 'react'; import '../App.css'; import './Dashboard.css'; -import { connect } from 'react-redux'; -import { getRecentScrobbles } from '../Actions/api'; +import { useHistory } from "react-router"; +import { getRecentScrobbles } from '../Api/index'; import ScaleLoader from 'react-spinners/ScaleLoader'; import ScrobbleTable from "../Components/ScrobbleTable"; +import AuthContext from '../Contexts/AuthContext'; -class Dashboard extends React.Component { - constructor(props) { - super(props); - this.state = { - isLoading: true, - scrobbleData: [], - uuid: null, - }; +const Dashboard = () => { + const history = useHistory(); + let { user } = useContext(AuthContext); + let [isLoading, setIsLoading] = useState(true); + let [dashboardData, setDashboardData] = useState({}); + + if (!user) { + history.push("/login"); } - componentDidMount() { - const { history, uuid } = this.props; - const isLoggedIn = this.props.isLoggedIn; - - if (!isLoggedIn) { - history.push("/login") + useEffect(() => { + if (!user) { + return } + getRecentScrobbles(user.uuid) + .then(data => { + setDashboardData(data); + setIsLoading(false); + }) + }, [user]) - getRecentScrobbles(uuid) - .then((data) => { - this.setState({ - isLoading: false, - data: data - }); - }) - .catch(() => { - this.setState({ - isLoading: false - }); - }); - } - - render() { - return ( -
-

- Dashboard! -

- {this.state.isLoading - ? - : - } -
- ); - } + return ( +
+

+ Dashboard! +

+ {isLoading + ? + : + } +
+ ); } -function mapStateToProps(state) { - const { isLoggedIn } = state.auth; - let uuid = null; - if (isLoggedIn) { - uuid = state.auth.user.uuid - } - - return { - isLoggedIn, - uuid, - }; -} - -export default connect(mapStateToProps)(Dashboard); +export default Dashboard; diff --git a/web/src/Pages/Home.js b/web/src/Pages/Home.js index 8bf4b070..574c57eb 100644 --- a/web/src/Pages/Home.js +++ b/web/src/Pages/Home.js @@ -4,16 +4,14 @@ import './Home.css'; import HomeBanner from '../Components/HomeBanner'; import React from 'react'; -class Home extends React.Component { - render() { - return ( +const Home = () => { + return (
logo

Go-Scrobble is an open source music scrobbling service written in Go and React.

); - } } export default Home; diff --git a/web/src/Pages/Login.js b/web/src/Pages/Login.js index 34e17791..ceb15dfb 100644 --- a/web/src/Pages/Login.js +++ b/web/src/Pages/Login.js @@ -1,102 +1,63 @@ -import React from 'react'; +import React, { useContext } from 'react'; import '../App.css'; import './Login.css'; import { Button } from 'reactstrap'; import { Formik, Form, Field } from 'formik'; import ScaleLoader from 'react-spinners/ScaleLoader'; -import { connect } from 'react-redux'; -import { login } from '../Actions/auth'; -import eventBus from "../Actions/eventBus"; -import { LOGIN_SUCCESS } from '../Actions/types'; +import AuthContext from '../Contexts/AuthContext'; +import { useHistory } from "react-router"; -class Login extends React.Component { - constructor(props) { - super(props); - this.state = {username: '', password: '', loading: false}; +const Login = () => { + const history = useHistory(); + let boolTrue = true; + let { Login, loading, user } = useContext(AuthContext); + + if (user) { + history.push("/dashboard"); } - componentDidMount() { - const { history, isLoggedIn } = this.props; - - if (isLoggedIn) { - history.push("/dashboard") - } - } - - handleLogin(values) { - this.setState({loading: true}); - - const { dispatch, history } = this.props; - - dispatch(login(values.username, values.password)) - .then(() => { - this.setState({ - loading: false, - isLoggedIn: true - }); - - eventBus.dispatch(LOGIN_SUCCESS, { isLoggedIn: true }); - history.push("/dashboard"); - }) - .catch(() => { - this.setState({ - loading: false - }); - }); - } - - render() { - let trueBool = true; - return ( -
-

- Login -

-
- this.handleLogin(values)} - > -
- -
- -

- -
-
-
+ return ( +
+

+ Login +

+
+ Login(values)} + > +
+ +
+ +

+ +
+
- ); - } +
+ ); } -function mapStateToProps(state) { - const { isLoggedIn } = state.auth; - return { - isLoggedIn - }; -} - -export default connect(mapStateToProps)(Login); +export default Login; diff --git a/web/src/Pages/Profile.js b/web/src/Pages/Profile.js index f87559b7..239f290d 100644 --- a/web/src/Pages/Profile.js +++ b/web/src/Pages/Profile.js @@ -1,33 +1,25 @@ -import React from 'react'; +import React, { useContext } from 'react'; import '../App.css'; import './Dashboard.css'; -import { connect } from 'react-redux'; +import { useHistory } from "react-router"; +import AuthContext from '../Contexts/AuthContext'; -class Profile extends React.Component { - componentDidMount() { - const { history, isLoggedIn } = this.props; - - if (!isLoggedIn) { - history.push("/login") - } +const Profile = () => { + const history = useHistory(); + const { user } = useContext(AuthContext); + + if (!user) { + history.push("/login"); } - render() { - return ( -
-

- Hai User -

-
- ); - } + return ( +
+

+ Welcome {user.username}! +

+
+ ); + } -function mapStateToProps(state) { - const { isLoggedIn } = state.auth; - return { - isLoggedIn, - }; -} - -export default connect(mapStateToProps)(Profile); \ No newline at end of file +export default Profile; \ No newline at end of file diff --git a/web/src/Pages/Register.js b/web/src/Pages/Register.js index 2c1ed49c..9787ebb6 100644 --- a/web/src/Pages/Register.js +++ b/web/src/Pages/Register.js @@ -1,125 +1,90 @@ -import React from 'react'; +import React, { useContext } from 'react'; import '../App.css'; import './Register.css'; import { Button } from 'reactstrap'; import ScaleLoader from "react-spinners/ScaleLoader"; -import { register } from '../Actions/auth'; +import AuthContext from '../Contexts/AuthContext'; import { Formik, Field, Form } from 'formik'; -import { connect } from 'react-redux'; +import { useHistory } from "react-router"; -class Register extends React.Component { - constructor(props) { - super(props); - this.state = {username: '', email: '', password: '', passwordconfirm: '', loading: false}; +const Register = () => { + const history = useHistory(); + let boolTrue = true; + let { Register, user, loading } = useContext(AuthContext); + + if (user) { + history.push("/dashboard"); } - componentDidMount() { - const { history, isLoggedIn } = this.props; - - if (isLoggedIn) { - history.push("/dashboard") - } - } - - handleRegister(values) { - console.log(values) - this.setState({loading: true}); - - const { dispatch, history } = this.props; - - dispatch(register(values.username, values.email, values.password)) - .then(() => { - this.setState({ - loading: false, - }); - history.push("/login"); - }) - .catch(() => { - this.setState({ - loading: false - }); - }); - } - - render() { - let trueBool = true; - return ( -
- { - // TODO: Move to DB:config REGISTRATION_DISABLED=1|0 - process.env.REACT_APP_REGISTRATION_DISABLED === "true" ? -

Registration is temporarily disabled. Please try again soon!

- : -
-

- Register -

-
- this.handleRegister(values)} - > -
- -
- -
- -
- -

- -
-
-
+ return ( +
+ { + // TODO: Move to DB:config REGISTRATION_DISABLED=1|0 :upsidedownsmile: + process.env.REACT_APP_REGISTRATION_DISABLED === "true" ? +

Registration is temporarily disabled. Please try again soon!

+ : +
+

+ Register +

+
+ Register(values)} + > +
+ +
+ +
+ +
+ +

+ +
+
- } -
- ); - } +
+ } +
+ ); } -function mapStateToProps(state) { - const { isLoggedIn } = state.auth; - return { - isLoggedIn - }; -} - -export default connect(mapStateToProps)(Register); +export default Register; diff --git a/web/src/Pages/Settings.js b/web/src/Pages/Settings.js index e517b361..791009e2 100644 --- a/web/src/Pages/Settings.js +++ b/web/src/Pages/Settings.js @@ -2,35 +2,19 @@ import React from 'react'; import '../App.css'; import './Settings.css'; -import { useToasts } from 'react-toast-notifications'; - -function withToast(Component) { - return function WrappedComponent(props) { - const toastFuncs = useToasts() - return ; - } -} - -class Settings extends React.Component { - constructor(props) { - super(props); - this.state = {username: '', password: '', loading: false}; - } - - render() { - return ( -
-

- Settings -

-
-

- All the settings -

-
+const Settings = () => { + return ( +
+

+ Settings +

+
+

+ All the settings +

- ); - } +
+ ); } -export default withToast(Settings); +export default Settings; diff --git a/web/src/Reducers/auth.js b/web/src/Reducers/auth.js deleted file mode 100644 index df75c1e6..00000000 --- a/web/src/Reducers/auth.js +++ /dev/null @@ -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; - } - } diff --git a/web/src/Reducers/index.js b/web/src/Reducers/index.js deleted file mode 100644 index 3df08848..00000000 --- a/web/src/Reducers/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import { combineReducers } from "redux"; -import auth from "./auth"; - -export default combineReducers({ - auth, -}); diff --git a/web/src/Services/Actions.js b/web/src/Services/Actions.js deleted file mode 100644 index e69de29b..00000000 diff --git a/web/src/Services/api.service.js b/web/src/Services/api.service.js deleted file mode 100644 index 0a202c5a..00000000 --- a/web/src/Services/api.service.js +++ /dev/null @@ -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(); \ No newline at end of file diff --git a/web/src/Services/auth-header.js b/web/src/Services/auth-header.js deleted file mode 100644 index 75454e14..00000000 --- a/web/src/Services/auth-header.js +++ /dev/null @@ -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 {}; - } -} diff --git a/web/src/Services/auth.service.js b/web/src/Services/auth.service.js deleted file mode 100644 index 81ce31a6..00000000 --- a/web/src/Services/auth.service.js +++ /dev/null @@ -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(); diff --git a/web/src/Services/user.service.js b/web/src/Services/user.service.js deleted file mode 100644 index 77b5391a..00000000 --- a/web/src/Services/user.service.js +++ /dev/null @@ -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(); diff --git a/web/src/index.js b/web/src/index.js index 03553c32..15dbe6a0 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -3,13 +3,14 @@ import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import { HashRouter } from 'react-router-dom'; + import { ToastContainer } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.min.css' -import { Provider } from 'react-redux'; -import store from "./store"; +import 'react-toastify/dist/ReactToastify.min.css'; + +import AuthContextProvider from './Contexts/AuthContextProvider'; ReactDOM.render( - + - , + , document.getElementById('root') ); diff --git a/web/src/store.js b/web/src/store.js deleted file mode 100644 index 03eff2c9..00000000 --- a/web/src/store.js +++ /dev/null @@ -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;