mirror of
https://github.com/idanoo/GoScrobble
synced 2025-07-03 06:32:19 +00:00
- Login flow working..
- Jellyfin scrobble working - Returns scrobbles via API for authed users /api/v1/user/{uuid}/scrobble - Add redis handler + funcs - Move middleware to pass in uuid as needed
This commit is contained in:
parent
c83c086cdd
commit
5fd9d41069
54 changed files with 1093 additions and 386 deletions
4
web/src/Pages/About.css
Normal file
4
web/src/Pages/About.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.aboutBody {
|
||||
padding: 20px 5px 5px 5px;
|
||||
font-size: 16pt;
|
||||
}
|
17
web/src/Pages/About.js
Normal file
17
web/src/Pages/About.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import '../App.css';
|
||||
import './About.css';
|
||||
|
||||
function About() {
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
<h1>
|
||||
About GoScrobble.com
|
||||
</h1>
|
||||
<p className="aboutBody">
|
||||
Go-Scrobble is an open source music scorbbling service written in Go and React.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default About;
|
45
web/src/Pages/Admin.js
Normal file
45
web/src/Pages/Admin.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
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;
|
4
web/src/Pages/Dashboard.css
Normal file
4
web/src/Pages/Dashboard.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.dashboardBody {
|
||||
padding: 20px 5px 5px 5px;
|
||||
font-size: 16pt;
|
||||
}
|
35
web/src/Pages/Dashboard.js
Normal file
35
web/src/Pages/Dashboard.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import '../App.css';
|
||||
import './Dashboard.css';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
class Dashboard extends React.Component {
|
||||
componentDidMount() {
|
||||
const { history } = this.props;
|
||||
const isLoggedIn = this.props.isLoggedIn;
|
||||
|
||||
if (!isLoggedIn) {
|
||||
history.push("/login")
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
<h1>
|
||||
Hai Dashboard!
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const { isLoggedIn } = state.auth;
|
||||
return {
|
||||
isLoggedIn,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Dashboard);
|
23
web/src/Pages/Home.js
Normal file
23
web/src/Pages/Home.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import logo from '../logo.png';
|
||||
import '../App.css';
|
||||
|
||||
function Home() {
|
||||
return (
|
||||
<div className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
15
web/src/Pages/Login.css
Normal file
15
web/src/Pages/Login.css
Normal file
|
@ -0,0 +1,15 @@
|
|||
.loginBody {
|
||||
padding: 20px 5px 5px 5px;
|
||||
font-size: 16pt;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.loginFields {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loginButton {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
margin-top:-5px;
|
||||
}
|
92
web/src/Pages/Login.js
Normal file
92
web/src/Pages/Login.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
import React 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';
|
||||
|
||||
class Login extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {username: '', password: '', loading: false};
|
||||
}
|
||||
|
||||
handleLogin(values) {
|
||||
this.setState({loading: true});
|
||||
|
||||
const { dispatch, history } = this.props;
|
||||
|
||||
dispatch(login(values.username, values.password))
|
||||
.then(() => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
history.push("/dashboard");
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let trueBool = true;
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
<h1>
|
||||
Login
|
||||
</h1>
|
||||
<div className="loginBody">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const { isLoggedIn } = state.auth;
|
||||
const { message } = state.message;
|
||||
return {
|
||||
isLoggedIn,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Login);
|
4
web/src/Pages/Profile.css
Normal file
4
web/src/Pages/Profile.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.profileBody {
|
||||
padding: 20px 5px 5px 5px;
|
||||
font-size: 16pt;
|
||||
}
|
35
web/src/Pages/Profile.js
Normal file
35
web/src/Pages/Profile.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import '../App.css';
|
||||
import './Dashboard.css';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
class Profile extends React.Component {
|
||||
componentDidMount() {
|
||||
const { history } = this.props;
|
||||
const isLoggedIn = this.props.isLoggedIn;
|
||||
|
||||
if (!isLoggedIn) {
|
||||
history.push("/login")
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
<h1>
|
||||
Hai User
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const { isLoggedIn } = state.auth;
|
||||
return {
|
||||
isLoggedIn,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Profile);
|
15
web/src/Pages/Register.css
Normal file
15
web/src/Pages/Register.css
Normal file
|
@ -0,0 +1,15 @@
|
|||
.loginBody {
|
||||
padding: 20px 5px 5px 5px;
|
||||
font-size: 16pt;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.loginFields {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loginButton {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
margin-top:-5px;
|
||||
}
|
160
web/src/Pages/Register.js
Normal file
160
web/src/Pages/Register.js
Normal file
|
@ -0,0 +1,160 @@
|
|||
import React from 'react';
|
||||
import '../App.css';
|
||||
import './Login.css';
|
||||
import { Button } from 'reactstrap';
|
||||
import ScaleLoader from "react-spinners/ScaleLoader";
|
||||
import { withRouter } from 'react-router-dom'
|
||||
|
||||
class Register extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {username: '', email: '', password: '', passwordconfirm: '', loading: false};
|
||||
this.handleUsernameChange = this.handleUsernameChange.bind(this);
|
||||
this.handleEmailChange = this.handleEmailChange.bind(this);
|
||||
this.handlePasswordChange = this.handlePasswordChange.bind(this);
|
||||
this.handlePasswordConfirmChange = this.handlePasswordConfirmChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
handleUsernameChange(event) {
|
||||
this.setState({username: event.target.value});
|
||||
}
|
||||
|
||||
handleEmailChange(event) {
|
||||
this.setState({email: event.target.value});
|
||||
}
|
||||
|
||||
handlePasswordChange(event) {
|
||||
this.setState({password: event.target.value});
|
||||
}
|
||||
|
||||
handlePasswordConfirmChange(event) {
|
||||
this.setState({passwordconfirm: event.target.value});
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.state.password !== this.state.passwordconfirm) {
|
||||
this.props.addToast('Passwords do not match', { appearance: 'error' });
|
||||
return
|
||||
}
|
||||
|
||||
// if (this.state.password.len < 8) {
|
||||
// this.props.addToast('Password must be at least 8 characters', { appearance: 'error' });
|
||||
// return
|
||||
// }
|
||||
|
||||
this.setState({loading: true});
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
timeout: 5000,
|
||||
body: JSON.stringify({
|
||||
username: this.state.username,
|
||||
email: this.state.email,
|
||||
password: this.state.password,
|
||||
})
|
||||
};
|
||||
|
||||
const apiUrl = process.env.REACT_APP_API_URL + '/api/v1/register';
|
||||
console.log(apiUrl);
|
||||
fetch(apiUrl, requestOptions)
|
||||
.then((response) => {
|
||||
if (response.status === 429) {
|
||||
this.props.addToast("Rate limited. Please try again soon", { appearance: 'error' });
|
||||
return "{}"
|
||||
} else {
|
||||
return response.json()
|
||||
}
|
||||
})
|
||||
.then((function(data) {
|
||||
console.log(data);
|
||||
if (data.error) {
|
||||
this.props.addToast(data.error, { appearance: 'error' });
|
||||
} else if (data.message) {
|
||||
this.props.addToast(data.message, { appearance: 'success' });
|
||||
this.props.history.push('/login')
|
||||
}
|
||||
this.setState({loading: false});
|
||||
}).bind(this))
|
||||
.catch(() => {
|
||||
this.props.addToast('Error submitting form. Please try again', { appearance: 'error' });
|
||||
this.setState({loading: false});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let trueBool = true;
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
{
|
||||
// TODO: Move to DB:config REGISTRATION_DISABLED=1|0
|
||||
process.env.REACT_APP_REGISTRATION_DISABLED === "true" ?
|
||||
<p>Registration is temporarily disabled. Please try again soon!</p>
|
||||
:
|
||||
<div>
|
||||
<h1>
|
||||
Register
|
||||
</h1>
|
||||
<div className="loginBody">
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<label>
|
||||
Username*<br/>
|
||||
<input
|
||||
type="text"
|
||||
required={trueBool}
|
||||
className="loginFields"
|
||||
value={this.state.username}
|
||||
onChange={this.handleUsernameChange}
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
<label>
|
||||
Email<br/>
|
||||
<input
|
||||
type="email"
|
||||
className="loginFields"
|
||||
value={this.state.email}
|
||||
onChange={this.handleEmailChange}
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
<label>
|
||||
Password*<br/>
|
||||
<input
|
||||
type="password"
|
||||
required={trueBool}
|
||||
className="loginFields"
|
||||
value={this.state.password}
|
||||
onChange={this.handlePasswordChange}
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
<label>
|
||||
Password*<br/>
|
||||
<input
|
||||
type="password"
|
||||
required={trueBool}
|
||||
className="loginFields"
|
||||
value={this.state.passwordconfirm}
|
||||
onChange={this.handlePasswordConfirmChange}
|
||||
/>
|
||||
</label>
|
||||
<br/><br/>
|
||||
<Button
|
||||
color="primary"
|
||||
type="submit"
|
||||
className="loginButton"
|
||||
disabled={this.state.loading}
|
||||
>{this.state.loading ? <ScaleLoader color="#FFF" size={35} /> : "Register"}</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Register);
|
0
web/src/Pages/Settings.css
Normal file
0
web/src/Pages/Settings.css
Normal file
36
web/src/Pages/Settings.js
Normal file
36
web/src/Pages/Settings.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
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 <Component {...props} {...toastFuncs} />;
|
||||
}
|
||||
}
|
||||
|
||||
class Settings extends React.Component {
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withToast(Settings);
|
Loading…
Add table
Add a link
Reference in a new issue