mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-22 08:25:14 +00:00
0.0.5
- Only allow ItemType:Audio from Jellyfin - Fix NavBar for Mobile (Ugly hack but.. TO REWORK) - Fixed registration page issues - Add functionality to pull recent scrobbles to Dashboard - Add MX record lookup validation for emails - Add username validation for a-Z 0-9 _ and . - Dashboard shows basic table of last 500 scrobbles.
This commit is contained in:
parent
7ae9a0cd66
commit
2f8aa2e502
@ -3,7 +3,7 @@ stages:
|
|||||||
- bundle
|
- bundle
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
VERSION: 0.0.4
|
VERSION: 0.0.5
|
||||||
|
|
||||||
build-go:
|
build-go:
|
||||||
image: golang:1.16.2
|
image: golang:1.16.2
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
# 0.0.5
|
||||||
|
- Only allow ItemType:Audio from Jellyfin
|
||||||
|
- Fix NavBar for Mobile (Ugly hack but.. TO REWORK)
|
||||||
|
- Fixed registration page issues
|
||||||
|
- Add functionality to pull recent scrobbles to Dashboard
|
||||||
|
- Add MX record lookup validation for emails
|
||||||
|
- Add username validation for a-Z 0-9 _ and .
|
||||||
|
- Dashboard shows basic table of last 500 scrobbles.
|
||||||
|
|
||||||
# 0.0.4
|
# 0.0.4
|
||||||
- Display stats on homepage
|
- Display stats on homepage
|
||||||
|
|
||||||
|
13
docs/removing_bad_data.md
Normal file
13
docs/removing_bad_data.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
## Removing bad data
|
||||||
|
|
||||||
|
This is by no means recommended.. But during testing I somehow scrobbled movies.
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS=0;
|
||||||
|
DELETE FROM artists WHERE `name` = "%!s(<nil>)";
|
||||||
|
DELETE FROM albums WHERE `name` = "%!s(<nil>)";
|
||||||
|
DELETE album_artist FROM album_artist LEFT JOIN artists ON artists.uuid = album_artist.artist WHERE artists.uuid is null;
|
||||||
|
DELETE album_artist FROM album_artist LEFT JOIN albums ON albums.uuid = album_artist.album WHERE albums.uuid is null;
|
||||||
|
DELETE track_artist FROM track_artist LEFT JOIN artists ON artists.uuid = track_artist.artist WHERE artists.uuid is null;
|
||||||
|
DELETE tracks FROM tracks LEFT JOIN track_artist ON track_artist.track = tracks.uuid WHERE track_artist.track IS NULL;
|
||||||
|
DELETE scrobbles FROM scrobbles LEFT JOIN tracks ON tracks.uuid = scrobbles.track WHERE tracks.uuid is null;
|
||||||
|
SET FOREIGN_KEY_CHECKS=1;
|
@ -10,6 +10,23 @@ import (
|
|||||||
|
|
||||||
// ParseJellyfinInput - Transform API data into a common struct
|
// ParseJellyfinInput - Transform API data into a common struct
|
||||||
func ParseJellyfinInput(userUUID string, data map[string]interface{}, ip net.IP, tx *sql.Tx) error {
|
func ParseJellyfinInput(userUUID string, data map[string]interface{}, ip net.IP, tx *sql.Tx) error {
|
||||||
|
if data["ItemType"] != "Audio" {
|
||||||
|
return errors.New("Media type not audio")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety Checks
|
||||||
|
if data["Artist"] == nil {
|
||||||
|
return errors.New("Missing artist data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if data["Album"] == nil {
|
||||||
|
return errors.New("Missing album data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if data["Name"] == nil {
|
||||||
|
return errors.New("Missing track data")
|
||||||
|
}
|
||||||
|
|
||||||
// Insert artist if not exist
|
// Insert artist if not exist
|
||||||
artist, err := insertArtist(fmt.Sprintf("%s", data["Artist"]), fmt.Sprintf("%s", data["Provider_musicbrainzartist"]), tx)
|
artist, err := insertArtist(fmt.Sprintf("%s", data["Artist"]), fmt.Sprintf("%s", data["Provider_musicbrainzartist"]), tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -165,14 +165,18 @@ func jwtMiddleware(next func(http.ResponseWriter, *http.Request, string, string)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var v string
|
var reqUuid string
|
||||||
for k, v := range mux.Vars(r) {
|
for k, v := range mux.Vars(r) {
|
||||||
if k == "id" {
|
if k == "id" {
|
||||||
log.Printf("key=%v, value=%v", k, v)
|
reqUuid = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next(w, r, claims.Subject, v)
|
if reqUuid == "" {
|
||||||
|
throwBadReq(w, "Invalid Request")
|
||||||
|
}
|
||||||
|
|
||||||
|
next(w, r, claims.Subject, reqUuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +210,7 @@ func handleRegister(w http.ResponseWriter, r *http.Request) {
|
|||||||
ip := getUserIp(r)
|
ip := getUserIp(r)
|
||||||
err = createUser(®Req, ip)
|
err = createUser(®Req, ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throwOkMessage(w, err.Error())
|
throwOkError(w, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,7 +269,7 @@ func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) {
|
|||||||
ip := getUserIp(r)
|
ip := getUserIp(r)
|
||||||
err := ParseJellyfinInput(userUuid, bodyJson, ip, tx)
|
err := ParseJellyfinInput(userUuid, bodyJson, ip, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error inserting track: %+v", err)
|
// log.Printf("Error inserting track: %+v", err)
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
throwBadReq(w, err.Error())
|
throwBadReq(w, err.Error())
|
||||||
return
|
return
|
||||||
|
@ -59,15 +59,12 @@ func createUser(req *RegisterRequest, ip net.IP) error {
|
|||||||
return errors.New("A username is required")
|
return errors.New("A username is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check max length for Username
|
// Check username is valid
|
||||||
if len(req.Username) > 64 {
|
if !isUsernameValid(req.Username) {
|
||||||
return errors.New("Username cannot be longer than 64 characters")
|
log.Println("user is invalid")
|
||||||
}
|
|
||||||
|
|
||||||
// Check username doesn't contain @
|
|
||||||
if strings.Contains(req.Username, "@") {
|
|
||||||
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 != "" {
|
||||||
|
@ -9,9 +9,11 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||||
|
var usernameRegex = regexp.MustCompile("^[a-zA-Z0-9_\\.]+$")
|
||||||
|
|
||||||
// decodeJson - Returns a map[string]interface{}
|
// decodeJson - Returns a map[string]interface{}
|
||||||
func decodeJson(body io.ReadCloser) (map[string]interface{}, error) {
|
func decodeJson(body io.ReadCloser) (map[string]interface{}, error) {
|
||||||
@ -24,10 +26,31 @@ func decodeJson(body io.ReadCloser) (map[string]interface{}, error) {
|
|||||||
|
|
||||||
// isEmailValid - checks if the email provided passes the required structure and length.
|
// isEmailValid - checks if the email provided passes the required structure and length.
|
||||||
func isEmailValid(e string) bool {
|
func isEmailValid(e string) bool {
|
||||||
if len(e) < 3 && len(e) > 254 {
|
if len(e) < 5 && len(e) > 254 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return emailRegex.MatchString(e)
|
|
||||||
|
if !emailRegex.MatchString(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do MX lookup
|
||||||
|
parts := strings.Split(e, "@")
|
||||||
|
mx, err := net.LookupMX(parts[1])
|
||||||
|
if err != nil || len(mx) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// isUsernameValid - Checks if username is alphanumeric+underscores+dots
|
||||||
|
func isUsernameValid(e string) bool {
|
||||||
|
if len(e) > 64 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return usernameRegex.MatchString(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// contains - Check if string is in list
|
// contains - Check if string is in list
|
||||||
|
40
web/package-lock.json
generated
40
web/package-lock.json
generated
@ -19,11 +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": "^1.0.1",
|
||||||
"react-toast-notifications": "^2.4.3",
|
"react-toast-notifications": "^2.4.3",
|
||||||
"react-toastify": "^7.0.3",
|
"react-toastify": "^7.0.3",
|
||||||
@ -16326,6 +16328,18 @@
|
|||||||
"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",
|
||||||
@ -16719,6 +16733,18 @@
|
|||||||
"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": {
|
"node_modules/react-toast": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-toast/-/react-toast-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-toast/-/react-toast-1.0.1.tgz",
|
||||||
@ -34981,6 +35007,14 @@
|
|||||||
"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",
|
||||||
@ -35297,6 +35331,12 @@
|
|||||||
"@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": {
|
"react-toast": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-toast/-/react-toast-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-toast/-/react-toast-1.0.1.tgz",
|
||||||
|
@ -14,11 +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": "^1.0.1",
|
||||||
"react-toast-notifications": "^2.4.3",
|
"react-toast-notifications": "^2.4.3",
|
||||||
"react-toastify": "^7.0.3",
|
"react-toastify": "^7.0.3",
|
||||||
|
@ -8,3 +8,11 @@ export const getStats = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getRecentScrobbles = (id) => {
|
||||||
|
return ApiService.getRecentScrobbles(id).then(
|
||||||
|
(data) => {
|
||||||
|
return data.data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { SET_MESSAGE, CLEAR_MESSAGE } from "./types";
|
|
||||||
|
|
||||||
export const setMessage = (message) => ({
|
|
||||||
type: SET_MESSAGE,
|
|
||||||
payload: message,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const clearMessage = () => ({
|
|
||||||
type: CLEAR_MESSAGE,
|
|
||||||
});
|
|
@ -3,6 +3,3 @@ export const REGISTER_FAIL = "REGISTER_FAIL";
|
|||||||
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
|
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
|
||||||
export const LOGIN_FAIL = "LOGIN_FAIL";
|
export const LOGIN_FAIL = "LOGIN_FAIL";
|
||||||
export const LOGOUT = "LOGOUT";
|
export const LOGOUT = "LOGOUT";
|
||||||
|
|
||||||
export const SET_MESSAGE = "SET_MESSAGE";
|
|
||||||
export const CLEAR_MESSAGE = "CLEAR_MESSAGE";
|
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
.pageWrapper {
|
.pageWrapper {
|
||||||
background-color: #282c34;
|
background-color: #282c34;
|
||||||
padding-top: 100px;
|
padding: 100px 15px 0 15px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -10,7 +10,7 @@ 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 { logout } from './Actions/auth';
|
||||||
import { Route, Switch, withRouter } from 'react-router-dom';
|
import { Route, Switch, withRouter } from 'react-router-dom';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
@ -26,34 +26,33 @@ function mapStateToProps(state) {
|
|||||||
class App extends Component {
|
class App extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.logOut = this.logOut.bind(this);
|
// this.logOut = this.logOut.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
// showAdminBoard: false,
|
// showAdminBoard: false,
|
||||||
currentUser: undefined,
|
// currentUser: undefined,
|
||||||
// Don't even ask.. apparently you can't pass
|
// Don't even ask.. apparently you can't pass
|
||||||
// exact="true".. it has to be a bool :|
|
// exact="true".. it has to be a bool :|
|
||||||
true: true,
|
true: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const user = this.props.user;
|
|
||||||
|
|
||||||
if (user) {
|
// componentDidMount() {
|
||||||
this.setState({
|
// const user = this.props.user;
|
||||||
currentUser: user,
|
|
||||||
// showAdminBoard: user.roles.includes("ROLE_ADMIN"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logOut() {
|
// if (user) {
|
||||||
this.props.dispatch(logout());
|
// this.setState({
|
||||||
}
|
// currentUser: user,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// logOut() {
|
||||||
|
// this.props.dispatch(logout());
|
||||||
|
// }
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// const { currentUser, showAdminBoard } = this.state;
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
@ -63,9 +62,10 @@ class App extends Component {
|
|||||||
|
|
||||||
<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="/admin" component={Admin} />
|
<Route path="/admin" component={Admin} />
|
||||||
|
|
||||||
<Route path="/settings" component={Settings} />
|
|
||||||
<Route path="/login" component={Login} />
|
<Route path="/login" component={Login} />
|
||||||
<Route path="/register" component={Register} />
|
<Route path="/register" component={Register} />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
.homeBanner {
|
.homeBanner {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: 1100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.homeBannerItem {
|
.homeBannerItem {
|
||||||
|
@ -39,22 +39,22 @@ class HomeBanner extends React.Component {
|
|||||||
<div className="homeBanner">
|
<div className="homeBanner">
|
||||||
<div className="homeBannerItem">
|
<div className="homeBannerItem">
|
||||||
{this.state.isLoading
|
{this.state.isLoading
|
||||||
? <ClipLoader color="#6AD7E5" size="36" />
|
? <ClipLoader color="#6AD7E5" size={36} />
|
||||||
: <span className="homeBannerItemCount">{this.state.scrobbleCount}</span>}<br/>Scrobbles
|
: <span className="homeBannerItemCount">{this.state.scrobbleCount}</span>}<br/>Scrobbles
|
||||||
</div>
|
</div>
|
||||||
<div className="homeBannerItem">
|
<div className="homeBannerItem">
|
||||||
{this.state.isLoading
|
{this.state.isLoading
|
||||||
? <ClipLoader color="#6AD7E5" size="36" />
|
? <ClipLoader color="#6AD7E5" size={36} />
|
||||||
: <span className="homeBannerItemCount">{this.state.userCount}</span>}<br/>Users
|
: <span className="homeBannerItemCount">{this.state.userCount}</span>}<br/>Users
|
||||||
</div>
|
</div>
|
||||||
<div className="homeBannerItem">
|
<div className="homeBannerItem">
|
||||||
{this.state.isLoading
|
{this.state.isLoading
|
||||||
? <ClipLoader color="#6AD7E5" size="36" />
|
? <ClipLoader color="#6AD7E5" size={36} />
|
||||||
: <span className="homeBannerItemCount">{this.state.trackCount}</span>}<br/>Tracks
|
: <span className="homeBannerItemCount">{this.state.trackCount}</span>}<br/>Tracks
|
||||||
</div>
|
</div>
|
||||||
<div className="homeBannerItem">
|
<div className="homeBannerItem">
|
||||||
{this.state.isLoading
|
{this.state.isLoading
|
||||||
? <ClipLoader color="#6AD7E5" size="36" />
|
? <ClipLoader color="#6AD7E5" size={36} />
|
||||||
: <span className="homeBannerItemCount">{this.state.artistCount}</span>}<br/>Artists
|
: <span className="homeBannerItemCount">{this.state.artistCount}</span>}<br/>Artists
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,19 +3,33 @@
|
|||||||
color: #CCCCCC;
|
color: #CCCCCC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navLinkMobile {
|
||||||
|
color: #CCCCCC;
|
||||||
|
}
|
||||||
|
|
||||||
.navLink:hover {
|
.navLink:hover {
|
||||||
color: #666666;
|
color: #666666;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navLinkMobile:hover {
|
||||||
|
color: #666666;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
.navLinkLogin {
|
.navLinkLogin {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
border-left: 1px solid #282c34;
|
border-left: 1px solid #282c34;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navLinkLoginMobile {
|
||||||
|
margin: 0 auto 0 auto;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-logo {
|
.nav-logo {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
margin: -15px 5px -15px -5px;
|
margin: -15px 5px -15px -5px;
|
||||||
|
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { React, Component } from 'react';
|
import { React, Component } from 'react';
|
||||||
import { Navbar, NavbarBrand } from 'reactstrap';
|
import { Navbar, NavbarBrand, Collapse, Nav, NavbarToggler, NavItem } from 'reactstrap';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import logo from '../logo.png';
|
import logo from '../logo.png';
|
||||||
import './Navigation.css';
|
import './Navigation.css';
|
||||||
@ -21,12 +21,19 @@ const loggedInMenuItems = [
|
|||||||
'Dashboard',
|
'Dashboard',
|
||||||
'About',
|
'About',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const isMobile = () => {
|
||||||
|
return (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent))
|
||||||
|
};
|
||||||
|
|
||||||
class Navigation extends Component {
|
class Navigation extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.toggleNavbar = this.toggleNavbar.bind(this);
|
||||||
|
|
||||||
// Yeah I know you might not hit home.. but I can't get the
|
// Yeah I know you might not hit home.. but I can't get the
|
||||||
// path based finder thing working on initial load :sweatsmile:
|
// path based finder thing working on initial load :sweatsmile:
|
||||||
this.state = { active: "Home" };
|
this.state = { active: "Home", collapsed: true};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -51,84 +58,150 @@ class Navigation extends Component {
|
|||||||
eventBus.remove(LOGOUT);
|
eventBus.remove(LOGOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_handleClick(menuItem) {
|
_handleClick(menuItem) {
|
||||||
this.setState({ active: menuItem });
|
this.setState({ active: menuItem, collapsed: !this.state.collapsed });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleNavbar() {
|
||||||
|
this.setState({ collapsed: !this.state.collapsed });
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a real mess. TO CLEAN UP.
|
||||||
render() {
|
render() {
|
||||||
const activeStyle = { color: '#FFFFFF' };
|
const activeStyle = { color: '#FFFFFF' };
|
||||||
|
|
||||||
const renderAuthButtons = () => {
|
const renderMobileNav = () => {
|
||||||
if (this.state.isLoggedIn) {
|
return <Navbar color="dark" dark fixed="top">
|
||||||
return <div className="navLinkLogin">
|
<NavbarBrand className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand>
|
||||||
|
<NavbarToggler onClick={this.toggleNavbar} className="mr-2" />
|
||||||
|
<Collapse isOpen={!this.state.collapsed} navbar>
|
||||||
|
{this.state.isLoggedIn ?
|
||||||
|
<Nav className="navLinkLoginMobile" navbar>
|
||||||
|
{loggedInMenuItems.map(menuItem =>
|
||||||
|
<NavItem>
|
||||||
<Link
|
<Link
|
||||||
to="/profile"
|
key={menuItem}
|
||||||
style={this.state.active === "profile" ? activeStyle : {}}
|
className="navLinkMobile"
|
||||||
onClick={this._handleClick.bind(this, "profile")}
|
style={this.state.active === menuItem ? activeStyle : {}}
|
||||||
className="navLink"
|
onClick={this._handleClick.bind(this, menuItem)}
|
||||||
>Profile</Link>
|
to={menuItem}
|
||||||
<Link to="/" className="navLink" onClick={logout()}>Logout</Link>
|
>{menuItem}</Link>
|
||||||
</div>;
|
</NavItem>
|
||||||
} else {
|
)}
|
||||||
return <div className="navLinkLogin">
|
<Link
|
||||||
<Link
|
to="/profile"
|
||||||
to="/login"
|
style={this.state.active === "profile" ? activeStyle : {}}
|
||||||
style={this.state.active === "login" ? activeStyle : {}}
|
onClick={this._handleClick.bind(this, "profile")}
|
||||||
onClick={this._handleClick.bind(this, "login")}
|
className="navLinkMobile"
|
||||||
className="navLink"
|
>Profile</Link>
|
||||||
>Login</Link>
|
<Link to="/" className="navLink" onClick={logout()}>Logout</Link>
|
||||||
<Link
|
</Nav>
|
||||||
to="/register"
|
: <Nav className="navLinkLoginMobile" navbar>
|
||||||
className="navLink"
|
{menuItems.map(menuItem =>
|
||||||
style={this.state.active === "register" ? activeStyle : {}}
|
<NavItem>
|
||||||
onClick={this._handleClick.bind(this, "register")}
|
<Link
|
||||||
history={this.props.history}
|
key={menuItem}
|
||||||
>Register</Link>
|
className="navLinkMobile"
|
||||||
</div>;
|
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 renderMenuButtons = () => {
|
const renderDesktopNav = () => {
|
||||||
if (this.state.isLoggedIn) {
|
return <Navbar color="dark" dark fixed="top">
|
||||||
return <div>
|
<NavbarBrand className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand>
|
||||||
{loggedInMenuItems.map(menuItem =>
|
{this.state.isLoggedIn ?
|
||||||
<Link
|
<div>
|
||||||
key={menuItem}
|
{loggedInMenuItems.map(menuItem =>
|
||||||
className="navLink"
|
<Link
|
||||||
style={this.state.active === menuItem ? activeStyle : {}}
|
key={menuItem}
|
||||||
onClick={this._handleClick.bind(this, menuItem)}
|
className="navLink"
|
||||||
to={menuItem}
|
style={this.state.active === menuItem ? activeStyle : {}}
|
||||||
>
|
onClick={this._handleClick.bind(this, menuItem)}
|
||||||
{menuItem}
|
to={menuItem}
|
||||||
</Link>
|
>
|
||||||
)}
|
{menuItem}
|
||||||
</div>;
|
</Link>
|
||||||
} else {
|
)}
|
||||||
return <div>
|
</div>
|
||||||
{menuItems.map(menuItem =>
|
: <div>
|
||||||
<Link
|
{menuItems.map(menuItem =>
|
||||||
key={menuItem}
|
<Link
|
||||||
className="navLink"
|
key={menuItem}
|
||||||
style={this.state.active === menuItem ? activeStyle : {}}
|
className="navLink"
|
||||||
onClick={this._handleClick.bind(this, menuItem)}
|
style={this.state.active === menuItem ? activeStyle : {}}
|
||||||
to={menuItem === "Home" ? "/" : menuItem}
|
onClick={this._handleClick.bind(this, menuItem)}
|
||||||
>
|
to={menuItem === "Home" ? "/" : menuItem}
|
||||||
{menuItem}
|
>
|
||||||
</Link>
|
{menuItem}
|
||||||
)}
|
</Link>
|
||||||
</div>;
|
)}
|
||||||
}
|
</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={logout()}>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>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Navbar color="dark" dark fixed="top">
|
{
|
||||||
<NavbarBrand className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand>
|
isMobile()
|
||||||
{renderMenuButtons()}
|
? renderMobileNav()
|
||||||
{renderAuthButtons()}
|
: renderDesktopNav()
|
||||||
</Navbar>
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
43
web/src/Components/ScrobbleTable.js
Normal file
43
web/src/Components/ScrobbleTable.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
class ScrobbleTable extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
data: this.props.data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table border={1} cellPadding={5}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Timestamp</td>
|
||||||
|
<td>Track</td>
|
||||||
|
<td>Artist</td>
|
||||||
|
<td>Album</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
this.state.data && this.state.data.items &&
|
||||||
|
this.state.data.items.map(function (element) {
|
||||||
|
return <tr>
|
||||||
|
<td>{element.time}</td>
|
||||||
|
<td>{element.track}</td>
|
||||||
|
<td>{element.artist}</td>
|
||||||
|
<td>{element.album}</td>
|
||||||
|
</tr>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ScrobbleTable;
|
@ -1,4 +1,4 @@
|
|||||||
.aboutBody {
|
.aboutBody {
|
||||||
padding: 20px 5px 5px 5px;
|
padding: 20px 0px 0px 0px;
|
||||||
font-size: 16pt;
|
font-size: 16pt;
|
||||||
}
|
}
|
@ -8,8 +8,16 @@ function About() {
|
|||||||
About GoScrobble.com
|
About GoScrobble.com
|
||||||
</h1>
|
</h1>
|
||||||
<p className="aboutBody">
|
<p className="aboutBody">
|
||||||
Go-Scrobble is an open source music scorbbling service written in Go and React.
|
Go-Scrobble is an open source music scorbbling service written in Go and React.<br/>
|
||||||
|
Used to track your listening history and build a profile to discover new music.
|
||||||
</p>
|
</p>
|
||||||
|
<a
|
||||||
|
className="aboutBodyw"
|
||||||
|
href="https://gitlab.com/idanoo/go-scrobble"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>gitlab.com/idanoo/go-scrobble
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,40 @@ import React from 'react';
|
|||||||
import '../App.css';
|
import '../App.css';
|
||||||
import './Dashboard.css';
|
import './Dashboard.css';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { getRecentScrobbles } from '../Actions/api';
|
||||||
|
import ScaleLoader from 'react-spinners/ScaleLoader';
|
||||||
|
import ScrobbleTable from "../Components/ScrobbleTable";
|
||||||
|
|
||||||
class Dashboard extends React.Component {
|
class Dashboard extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isLoading: true,
|
||||||
|
scrobbleData: [],
|
||||||
|
uuid: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { history } = this.props;
|
const { history, uuid } = this.props;
|
||||||
const isLoggedIn = this.props.isLoggedIn;
|
const isLoggedIn = this.props.isLoggedIn;
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
history.push("/login")
|
history.push("/login")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRecentScrobbles(uuid)
|
||||||
|
.then((data) => {
|
||||||
|
this.setState({
|
||||||
|
isLoading: false,
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.setState({
|
||||||
|
isLoading: false
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -19,6 +44,10 @@ class Dashboard extends React.Component {
|
|||||||
<h1>
|
<h1>
|
||||||
Dashboard!
|
Dashboard!
|
||||||
</h1>
|
</h1>
|
||||||
|
{this.state.isLoading
|
||||||
|
? <ScaleLoader color="#FFF" size={60} />
|
||||||
|
: <ScrobbleTable data={this.state.data} />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -26,8 +55,11 @@ class Dashboard extends React.Component {
|
|||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
const { isLoggedIn } = state.auth;
|
const { isLoggedIn } = state.auth;
|
||||||
|
const { uuid } = state.auth.user;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
|
uuid,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
0
web/src/Pages/Home.css
Normal file
0
web/src/Pages/Home.css
Normal file
@ -1,24 +1,15 @@
|
|||||||
import logo from '../logo.png';
|
import logo from '../logo.png';
|
||||||
import '../App.css';
|
import '../App.css';
|
||||||
|
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 {
|
class Home extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="App-header">
|
<div className="pageWrapper">
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
<img src={logo} className="App-logo" alt="logo" />
|
||||||
<p>
|
<p className="homeText">Go-Scrobble is an open source music scrobbling service written in Go and React.</p>
|
||||||
goscrobble.com
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
className="App-link"
|
|
||||||
href="https://gitlab.com/idanoo/go-scrobble"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
gitlab.com/idanoo/go-scrobble
|
|
||||||
</a>
|
|
||||||
<HomeBanner />
|
<HomeBanner />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
.loginBody {
|
.registerBody {
|
||||||
padding: 20px 5px 5px 5px;
|
padding: 20px 5px 5px 5px;
|
||||||
font-size: 16pt;
|
font-size: 16pt;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loginFields {
|
.registerFields {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loginButton {
|
.registerButton {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top:-5px;
|
margin-top:-5px;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import '../App.css';
|
import '../App.css';
|
||||||
import './Login.css';
|
import './Register.css';
|
||||||
import { Button, Form } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
import ScaleLoader from "react-spinners/ScaleLoader";
|
import ScaleLoader from "react-spinners/ScaleLoader";
|
||||||
import { register } from '../Actions/auth';
|
import { register } from '../Actions/auth';
|
||||||
import { Formik, Field } from 'formik';
|
import { Formik, Field, Form } from 'formik';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
class Register extends React.Component {
|
class Register extends React.Component {
|
||||||
@ -22,6 +22,7 @@ class Register extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleRegister(values) {
|
handleRegister(values) {
|
||||||
|
console.log(values)
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
|
|
||||||
const { dispatch, history } = this.props;
|
const { dispatch, history } = this.props;
|
||||||
@ -53,10 +54,11 @@ class Register extends React.Component {
|
|||||||
<h1>
|
<h1>
|
||||||
Register
|
Register
|
||||||
</h1>
|
</h1>
|
||||||
<div className="loginBody">
|
<div className="registerBody">
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{ username: '', email: '', password: '', passwordconfirm: '' }}
|
initialValues={{ username: '', email: '', password: '', passwordconfirm: '' }}
|
||||||
onSubmit={async values => this.handleRegister(values)}>
|
onSubmit={async values => this.handleRegister(values)}
|
||||||
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<label>
|
<label>
|
||||||
Username*<br/>
|
Username*<br/>
|
||||||
@ -64,7 +66,7 @@ class Register extends React.Component {
|
|||||||
name="username"
|
name="username"
|
||||||
type="text"
|
type="text"
|
||||||
required={trueBool}
|
required={trueBool}
|
||||||
className="loginFields"
|
className="registerFields"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
@ -73,7 +75,7 @@ class Register extends React.Component {
|
|||||||
<Field
|
<Field
|
||||||
name="email"
|
name="email"
|
||||||
type="email"
|
type="email"
|
||||||
className="loginFields"
|
className="registerFields"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
@ -83,7 +85,7 @@ class Register extends React.Component {
|
|||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
required={trueBool}
|
required={trueBool}
|
||||||
className="loginFields"
|
className="registerFields"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
@ -93,14 +95,14 @@ class Register extends React.Component {
|
|||||||
name="passwordconfirm"
|
name="passwordconfirm"
|
||||||
type="password"
|
type="password"
|
||||||
required={trueBool}
|
required={trueBool}
|
||||||
className="loginFields"
|
className="registerFields"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
className="loginButton"
|
className="registerButton"
|
||||||
disabled={this.state.loading}
|
disabled={this.state.loading}
|
||||||
>{this.state.loading ? <ScaleLoader color="#FFF" size={35} /> : "Register"}</Button>
|
>{this.state.loading ? <ScaleLoader color="#FFF" size={35} /> : "Register"}</Button>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { combineReducers } from "redux";
|
import { combineReducers } from "redux";
|
||||||
import auth from "./auth";
|
import auth from "./auth";
|
||||||
import message from "./message";
|
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
auth,
|
auth,
|
||||||
message,
|
|
||||||
});
|
});
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import { SET_MESSAGE, CLEAR_MESSAGE } from "../Actions/types";
|
|
||||||
|
|
||||||
const initialState = {};
|
|
||||||
|
|
||||||
export default function message(state = initialState, action) {
|
|
||||||
const { type, payload } = action;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case SET_MESSAGE:
|
|
||||||
return { message: payload };
|
|
||||||
|
|
||||||
case CLEAR_MESSAGE:
|
|
||||||
return { message: "" };
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,14 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import authHeader from '../Services/auth-header';
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
async getStats() {
|
async getStats() {
|
||||||
return axios.get(process.env.REACT_APP_API_URL + "stats");
|
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();
|
export default new ApiService();
|
@ -1,8 +1,8 @@
|
|||||||
export default function authHeader() {
|
export default function authHeader() {
|
||||||
const auth = localStorage.getItem('user');
|
const user = JSON.parse(localStorage.getItem('user'));
|
||||||
|
|
||||||
if (auth && auth.jwt) {
|
if (user && user.jwt) {
|
||||||
return { Authorization: 'Bearer ' + auth.jwt };
|
return { Authorization: 'Bearer ' + user.jwt };
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import axios from "axios";
|
|||||||
import jwt from 'jwt-decode' // import dependency
|
import jwt from 'jwt-decode' // import dependency
|
||||||
|
|
||||||
class AuthService {
|
class AuthService {
|
||||||
login(username, password) {
|
async login(username, password) {
|
||||||
return axios
|
return axios
|
||||||
.post(process.env.REACT_APP_API_URL + "login", { username, password })
|
.post(process.env.REACT_APP_API_URL + "login", { username, password })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@ -24,11 +24,16 @@ class AuthService {
|
|||||||
localStorage.removeItem("user");
|
localStorage.removeItem("user");
|
||||||
}
|
}
|
||||||
|
|
||||||
register(username, email, password) {
|
async register(username, email, password) {
|
||||||
return axios.post(process.env.REACT_APP_API_URL + "register", {
|
return axios
|
||||||
|
.post(process.env.REACT_APP_API_URL + "register", {
|
||||||
username,
|
username,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
console.log(response)
|
||||||
|
return response.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user