mirror of
https://github.com/idanoo/GoScrobble
synced 2025-07-01 05:32:18 +00:00
0.0.10
- Fixed looking up invalid profiles - Added valid error handling to bad request && rate limiting - Add Sendgrid library (Will add SMTP later) - Complete password reset process
This commit is contained in:
parent
e570314ac2
commit
fd615102a8
28 changed files with 871 additions and 261 deletions
|
@ -32,9 +32,14 @@ export const PostLogin = (formValues) => {
|
|||
toast.error(response.data.error ? response.data.error: 'An Unknown Error has occurred');
|
||||
return null
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Failed to connect');
|
||||
}).catch((error) => {
|
||||
if (error.response === 401) {
|
||||
return {};
|
||||
} else if (error.response === 429) {
|
||||
toast.error('Rate limited. Please try again shortly')
|
||||
} else {
|
||||
toast.error('Failed to connect');
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
};
|
||||
|
@ -51,20 +56,70 @@ export const PostRegister = (formValues) => {
|
|||
|
||||
return Promise.reject();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
}).catch((error) => {
|
||||
if (error.response === 401) {
|
||||
return {};
|
||||
} else if (error.response === 429) {
|
||||
toast.error('Rate limited. Please try again shortly')
|
||||
} else {
|
||||
toast.error('Failed to connect');
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
export const PostResetPassword = (formValues) => {
|
||||
return axios.post(process.env.REACT_APP_API_URL + "resetpassword", 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((error) => {
|
||||
if (error.response === 401) {
|
||||
return {};
|
||||
} else if (error.response === 429) {
|
||||
toast.error('Rate limited. Please try again shortly')
|
||||
} else {
|
||||
toast.error('Failed to connect');
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
export const sendPasswordReset = (values) => {
|
||||
return axios.post(process.env.REACT_APP_API_URL + "sendreset", values).then(
|
||||
(data) => {
|
||||
return data.data;
|
||||
}).catch((error) => {
|
||||
if (error.response === 401) {
|
||||
return {};
|
||||
} else if (error.response === 429) {
|
||||
toast.error('Rate limited. Please try again shortly')
|
||||
} else {
|
||||
toast.error('Failed to connect');
|
||||
}
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
export const getStats = () => {
|
||||
return axios.get(process.env.REACT_APP_API_URL + "stats").then(
|
||||
(data) => {
|
||||
return data.data;
|
||||
}).catch((error) => {
|
||||
if (error.response === 401) {
|
||||
return {};
|
||||
} else if (error.response === 429) {
|
||||
toast.error('Rate limited. Please try again shortly')
|
||||
} else {
|
||||
toast.error('Failed to connect');
|
||||
}
|
||||
).catch(() => {
|
||||
toast.error('Failed to connect');
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
@ -73,8 +128,14 @@ export const getRecentScrobbles = (id) => {
|
|||
return axios.get(process.env.REACT_APP_API_URL + "user/" + id + "/scrobbles", { headers: getHeaders() })
|
||||
.then((data) => {
|
||||
return data.data;
|
||||
}).catch(() => {
|
||||
toast.error('Failed to connect');
|
||||
}).catch((error) => {
|
||||
if (error.response === 401) {
|
||||
return {};
|
||||
} else if (error.response === 429) {
|
||||
toast.error('Rate limited. Please try again shortly')
|
||||
} else {
|
||||
toast.error('Failed to connect');
|
||||
}
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
@ -83,8 +144,14 @@ export const getConfigs = () => {
|
|||
return axios.get(process.env.REACT_APP_API_URL + "config", { headers: getHeaders() })
|
||||
.then((data) => {
|
||||
return data.data;
|
||||
}).catch(() => {
|
||||
toast.error('Failed to connect');
|
||||
}).catch((error) => {
|
||||
if (error.response === 401) {
|
||||
return {};
|
||||
} else if (error.response === 429) {
|
||||
toast.error('Rate limited. Please try again shortly')
|
||||
} else {
|
||||
toast.error('Failed to connect');
|
||||
}
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
@ -103,18 +170,30 @@ export const postConfigs = (values, toggle) => {
|
|||
} else if (data.data && data.data.error) {
|
||||
toast.error(data.data.error);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Error updating values');
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response === 401) {
|
||||
return {};
|
||||
} else if (error.response === 429) {
|
||||
toast.error('Rate limited. Please try again shortly')
|
||||
} else {
|
||||
toast.error('Failed to connect');
|
||||
}
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
export const getProfile = (userName) => {
|
||||
return axios.get(process.env.REACT_APP_API_URL + "profile/" + userName, { headers: getHeaders() })
|
||||
.then((data) => {
|
||||
return data.data;
|
||||
}).catch(() => {
|
||||
toast.error('Failed to connect');
|
||||
}).catch((error) => {
|
||||
if (error.response === 401) {
|
||||
return {};
|
||||
} else if (error.response === 429) {
|
||||
toast.error('Rate limited. Please try again shortly')
|
||||
} else {
|
||||
toast.error('Failed to connect');
|
||||
}
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
@ -123,8 +202,35 @@ export const getUser = () => {
|
|||
return axios.get(process.env.REACT_APP_API_URL + "user", { headers: getHeaders() })
|
||||
.then((data) => {
|
||||
return data.data;
|
||||
}).catch(() => {
|
||||
toast.error('Failed to connect');
|
||||
}).catch((error) => {
|
||||
if (error.response === 401) {
|
||||
return {};
|
||||
} else if (error.response === 429) {
|
||||
toast.error('Rate limited. Please try again shortly')
|
||||
} else {
|
||||
toast.error('Failed to connect');
|
||||
}
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
export const validateResetPassword = (tokenStr) => {
|
||||
return axios.post(process.env.REACT_APP_API_URL + "resetpassword", { token: tokenStr })
|
||||
.then((data) => {
|
||||
if (data.error) {
|
||||
toast.error(data.error);
|
||||
return {valid: false}
|
||||
}
|
||||
return data.data;
|
||||
}).catch((error) => {
|
||||
if (error.response === 401) {
|
||||
return {};
|
||||
} else if (error.response === 429) {
|
||||
toast.error('Rate limited. Please try again shortly')
|
||||
} else {
|
||||
toast.error('Failed to connect');
|
||||
}
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import User from './Pages/User';
|
|||
import Admin from './Pages/Admin';
|
||||
import Login from './Pages/Login';
|
||||
import Register from './Pages/Register';
|
||||
import Reset from './Pages/Reset';
|
||||
|
||||
import Navigation from './Components/Navigation';
|
||||
|
||||
|
@ -38,6 +39,9 @@ const App = () => {
|
|||
<Route path="/login" component={Login} />
|
||||
<Route path="/register" component={Register} />
|
||||
|
||||
<Route path="/reset/:token" component={Reset} />
|
||||
<Route path="/reset" component={Reset} />
|
||||
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import AuthContext from './AuthContext';
|
||||
import { PostLogin, PostRegister } from '../Api/index';
|
||||
import { PostLogin, PostRegister, PostResetPassword } from '../Api/index';
|
||||
|
||||
const AuthContextProvider = ({ children }) => {
|
||||
const [user, setUser] = useState();
|
||||
|
@ -30,11 +30,14 @@ const AuthContextProvider = ({ children }) => {
|
|||
const Register = (formValues) => {
|
||||
setLoading(true);
|
||||
return PostRegister(formValues).then(response => {
|
||||
// Do stuff here?
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const ResetPassword = (formValues) => {
|
||||
return PostResetPassword(formValues)
|
||||
}
|
||||
|
||||
const Logout = () => {
|
||||
localStorage.removeItem("user");
|
||||
setUser(null)
|
||||
|
@ -47,6 +50,7 @@ const AuthContextProvider = ({ children }) => {
|
|||
Logout,
|
||||
Login,
|
||||
Register,
|
||||
ResetPassword,
|
||||
loading,
|
||||
user,
|
||||
}}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useContext, useState, useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import '../App.css';
|
||||
import './Login.css';
|
||||
import { Button } from 'reactstrap';
|
||||
|
@ -9,6 +10,7 @@ import { Switch } from 'formik-material-ui';
|
|||
import { getConfigs, postConfigs } from '../Api/index'
|
||||
|
||||
const Admin = () => {
|
||||
const history = useHistory();
|
||||
const { user } = useContext(AuthContext);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [configs, setConfigs] = useState({})
|
||||
|
@ -38,11 +40,7 @@ const Admin = () => {
|
|||
}
|
||||
|
||||
if (!user || !user.admin) {
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
<h1>Unauthorized</h1>
|
||||
</div>
|
||||
)
|
||||
history.push("/login")
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -8,7 +8,7 @@ import ScrobbleTable from "../Components/ScrobbleTable";
|
|||
import AuthContext from '../Contexts/AuthContext';
|
||||
|
||||
const Dashboard = () => {
|
||||
const history = useHistory();
|
||||
// const history = useHistory();
|
||||
let { user } = useContext(AuthContext);
|
||||
let [loading, setLoading] = useState(true);
|
||||
let [dashboardData, setDashboardData] = useState({});
|
||||
|
|
|
@ -16,6 +16,10 @@ const Login = () => {
|
|||
history.push("/dashboard");
|
||||
}
|
||||
|
||||
const redirectReset = () => {
|
||||
history.push("/reset")
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
<h1>
|
||||
|
@ -42,7 +46,6 @@ const Login = () => {
|
|||
<Field
|
||||
name="password"
|
||||
type="password"
|
||||
required={boolTrue}
|
||||
className="loginFields"
|
||||
/>
|
||||
</label>
|
||||
|
@ -53,6 +56,14 @@ const Login = () => {
|
|||
className="loginButton"
|
||||
disabled={loading}
|
||||
>{loading ? <ScaleLoader color="#FFF" size={35} /> : "Login"}</Button>
|
||||
<br/><br/>
|
||||
<Button
|
||||
color="secondary"
|
||||
type="button"
|
||||
className="loginButton"
|
||||
onClick={redirectReset}
|
||||
disabled={loading}
|
||||
>{loading ? <ScaleLoader color="#FFF" size={35} /> : "Reset Password"}</Button>
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
|
|
|
@ -35,7 +35,7 @@ const Profile = (route) => {
|
|||
)
|
||||
}
|
||||
|
||||
if (!username || Object.keys(profile).length === 0) {
|
||||
if (!username || !profile.username) {
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
Unable to fetch user
|
||||
|
@ -50,7 +50,7 @@ const Profile = (route) => {
|
|||
</h1>
|
||||
<div className="profileBody">
|
||||
Last 10 scrobbles...<br/>
|
||||
<ScrobbleTable data={profile.scrobbles}/>
|
||||
<ScrobbleTable data={profile.scrobbles}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
15
web/src/Pages/Reset.css
Normal file
15
web/src/Pages/Reset.css
Normal file
|
@ -0,0 +1,15 @@
|
|||
.resetBody {
|
||||
padding: 20px 5px 5px 5px;
|
||||
font-size: 16pt;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.resetFields {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.resetButton {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
margin-top:-5px;
|
||||
}
|
153
web/src/Pages/Reset.js
Normal file
153
web/src/Pages/Reset.js
Normal file
|
@ -0,0 +1,153 @@
|
|||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import '../App.css';
|
||||
import './Reset.css';
|
||||
import { Button } from 'reactstrap';
|
||||
import { Formik, Form, Field } from 'formik';
|
||||
import ScaleLoader from 'react-spinners/ScaleLoader';
|
||||
import { validateResetPassword, sendPasswordReset } from '../Api/index';
|
||||
import AuthContext from '../Contexts/AuthContext';
|
||||
|
||||
const Reset = (route) => {
|
||||
let boolTrue = true;
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [reset, setReset] = useState({});
|
||||
const [sent, setSent] = useState(false);
|
||||
|
||||
let { ResetPassword } = useContext(AuthContext);
|
||||
|
||||
let reqToken = false;
|
||||
if (route && route.match && route.match.params && route.match.params.token) {
|
||||
reqToken = route.match.params.token
|
||||
}
|
||||
|
||||
const sendReset = (values) => {
|
||||
sendPasswordReset(values).then(() => {
|
||||
setSent(true);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!reqToken) {
|
||||
setLoading(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
validateResetPassword(reqToken)
|
||||
.then(data => {
|
||||
setReset(data);
|
||||
console.log(data)
|
||||
setLoading(false);
|
||||
})
|
||||
}, [reqToken])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
<ScaleLoader color="#6AD7E5" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (sent) {
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
<h1>
|
||||
Check your email!
|
||||
</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!reqToken) {
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
<h1>
|
||||
Reset Password
|
||||
</h1>
|
||||
<div className="loginBody">
|
||||
<Formik
|
||||
initialValues={{ email: '' }}
|
||||
onSubmit={values => sendReset(values)}
|
||||
>
|
||||
<Form>
|
||||
<label>
|
||||
Email<br/>
|
||||
<Field
|
||||
name="email"
|
||||
type="email"
|
||||
required={boolTrue}
|
||||
className="loginFields"
|
||||
/>
|
||||
</label>
|
||||
<br/><br/>
|
||||
<Button
|
||||
color="primary"
|
||||
type="submit"
|
||||
className="loginButton"
|
||||
disabled={loading}
|
||||
>{loading ? <ScaleLoader color="#FFF" size={35} /> : "Reset"}</Button>
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (reqToken && !reset.valid) {
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
Invalid Reset Token or Token expired
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
<h1>
|
||||
Reset Password
|
||||
</h1>
|
||||
<div className="resetBody">
|
||||
<Formik
|
||||
initialValues={{ password: '', comfirmpassword: '', token: reqToken }}
|
||||
onSubmit={values => ResetPassword(values)}
|
||||
>
|
||||
<Form>
|
||||
<label>
|
||||
New Password<br/>
|
||||
<Field
|
||||
name="password"
|
||||
type="password"
|
||||
required={boolTrue}
|
||||
className="resetFields"
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
<label>
|
||||
Confirm New Password<br/>
|
||||
<Field
|
||||
name="comfirmpassword"
|
||||
type="password"
|
||||
required={boolTrue}
|
||||
className="resetFields"
|
||||
/>
|
||||
</label>
|
||||
<Field
|
||||
name="token"
|
||||
type="hidden"
|
||||
className="resetFields"
|
||||
/>
|
||||
<br/><br/>
|
||||
<Button
|
||||
color="primary"
|
||||
type="submit"
|
||||
className="loginButton"
|
||||
disabled={loading}
|
||||
>{loading ? <ScaleLoader color="#FFF" size={35} /> : "Reset"}</Button>
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Reset;
|
Loading…
Add table
Add a link
Reference in a new issue