- 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:
Daniel Mason 2021-03-29 20:56:34 +13:00
parent c83c086cdd
commit 5fd9d41069
Signed by: idanoo
GPG key ID: 387387CDBC02F132
54 changed files with 1093 additions and 386 deletions

4
web/src/Pages/About.css Normal file
View file

@ -0,0 +1,4 @@
.aboutBody {
padding: 20px 5px 5px 5px;
font-size: 16pt;
}

17
web/src/Pages/About.js Normal file
View 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
View 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;

View file

@ -0,0 +1,4 @@
.dashboardBody {
padding: 20px 5px 5px 5px;
font-size: 16pt;
}

View 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
View 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
View 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
View 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);

View file

@ -0,0 +1,4 @@
.profileBody {
padding: 20px 5px 5px 5px;
font-size: 16pt;
}

35
web/src/Pages/Profile.js Normal file
View 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);

View 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
View 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);

View file

36
web/src/Pages/Settings.js Normal file
View 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);