- Fix mobile menu auto collapse on select
- Add /u/ route for public user profiles (Added private flag to db - to implement later)
- Add /user route for your own profile / edit profile
- Added handling for if API is offline/incorrect
- Add index.html loading spinner while react bundle downloads
- Change HashRouter to BrowserRouter
- Added sources column to scrobbles
This commit is contained in:
Daniel Mason 2021-04-01 23:17:46 +13:00
parent af02bd99cc
commit e570314ac2
Signed by: idanoo
GPG key ID: 387387CDBC02F132
27 changed files with 435 additions and 89 deletions

View file

@ -14,8 +14,6 @@ function getHeaders() {
}
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) {
@ -36,6 +34,7 @@ export const PostLogin = (formValues) => {
}
})
.catch(() => {
toast.error('Failed to connect');
return Promise.resolve();
});
};
@ -54,6 +53,7 @@ export const PostRegister = (formValues) => {
}
})
.catch(() => {
toast.error('Failed to connect');
return Promise.resolve();
});
};
@ -61,16 +61,21 @@ export const PostRegister = (formValues) => {
export const getStats = () => {
return axios.get(process.env.REACT_APP_API_URL + "stats").then(
(data) => {
data.isLoading = false;
return data.data;
}
);
).catch(() => {
toast.error('Failed to connect');
return {};
});
};
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');
return {};
});
};
@ -78,6 +83,9 @@ 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');
return {};
});
};
@ -101,3 +109,22 @@ export const postConfigs = (values, toggle) => {
});
};
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');
return {};
});
};
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');
return {};
});
};

View file

@ -1,3 +1,7 @@
html, body {
background-color: #282c34;
}
.App {
text-align: center;
}

View file

@ -1,9 +1,9 @@
import { Route, Switch, withRouter } from 'react-router-dom';
import Home from './Pages/Home';
import About from './Pages/About';
import Dashboard from './Pages/Dashboard';
import Profile from './Pages/Profile';
import User from './Pages/User';
import Admin from './Pages/Admin';
import Login from './Pages/Login';
import Register from './Pages/Register';
@ -14,7 +14,13 @@ import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
const App = () => {
let boolTrue = true
let boolTrue = true;
// Remove loading spinner on load
const el = document.querySelector(".loader-container");
if (el) {
el.remove();
}
return (
<div>
@ -24,7 +30,8 @@ const App = () => {
<Route path="/about" component={About} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/profile" component={Profile} />
<Route path="/user" component={User} />
<Route path="/u/:uuid" component={Profile} />
<Route path="/admin" component={Admin} />

View file

@ -11,8 +11,10 @@ const HomeBanner = () => {
useEffect(() => {
getStats()
.then(data => {
setBannerData(data);
setIsLoading(false);
if (data.users) {
setBannerData(data);
setIsLoading(false);
}
})
}, [])

View file

@ -45,38 +45,41 @@ const Navigation = () => {
{user ?
<Nav className="navLinkLoginMobile" navbar>
{loggedInMenuItems.map(menuItem =>
<NavItem>
<Link
<NavItem key={menuItem}>
<Link
key={menuItem}
className="navLinkMobile"
style={active === menuItem ? activeStyle : {}}
to={menuItem}
onClick={toggleCollapsed}
>{menuItem}</Link>
</NavItem>
)}
<Link
to="/profile"
style={active === "profile" ? activeStyle : {}}
to="/user"
style={active === "user" ? activeStyle : {}}
className="navLinkMobile"
>Profile</Link>
onClick={toggleCollapsed}
>{user.username}</Link>
{user.admin &&
<Link
to="/admin"
style={active === "admin" ? activeStyle : {}}
className="navLink"
onClick={toggleCollapsed}
>Admin</Link>}
<Link to="/" className="navLink" onClick={Logout}>Logout</Link>
</Nav>
: <Nav className="navLinkLoginMobile" navbar>
{menuItems.map(menuItem =>
<NavItem>
<NavItem key={menuItem}>
<Link
key={menuItem}
className="navLinkMobile"
style={active === menuItem ? activeStyle : {}}
to={menuItem === "Home" ? "/" : menuItem}
>
{menuItem}
onClick={toggleCollapsed}
>{menuItem}
</Link>
</NavItem>
)}
@ -85,6 +88,7 @@ const Navigation = () => {
to="/Login"
style={active === "Login" ? activeStyle : {}}
className="navLinkMobile"
onClick={toggleCollapsed}
>Login</Link>
</NavItem>
<NavItem>
@ -92,6 +96,7 @@ const Navigation = () => {
to="/Register"
className="navLinkMobile"
style={active === "Register" ? activeStyle : {}}
onClick={toggleCollapsed}
>Register</Link>
</NavItem>
</Nav>
@ -132,8 +137,8 @@ const Navigation = () => {
{user ?
<div className="navLinkLogin">
<Link
to="/profile"
style={active === "profile" ? activeStyle : {}}
to="/user"
style={active === "user" ? activeStyle : {}}
className="navLink"
>{user.username}</Link>
{user.admin &&

View file

@ -14,9 +14,9 @@ const ScrobbleTable = (props) => {
</thead>
<tbody>
{
props.data && props.data.items &&
props.data.items.map(function (element) {
return <tr>
props.data &&
props.data.map(function (element) {
return <tr key={element.uuid}>
<td>{element.time}</td>
<td>{element.track}</td>
<td>{element.artist}</td>

View file

@ -17,19 +17,17 @@ const Admin = () => {
useEffect(() => {
getConfigs()
.then(data => {
setConfigs(data.configs);
setToggle(data.configs.REGISTRATION_ENABLED === "1")
if (data.configs) {
setConfigs(data.configs);
setToggle(data.configs.REGISTRATION_ENABLED === "1")
}
setLoading(false);
})
}, [])
if (!user || !user.admin) {
return (
<div className="pageWrapper">
<h1>Unauthorized</h1>
</div>
)
}
const handleToggle = () => {
setToggle(!toggle);
};
if (loading) {
return (
@ -39,9 +37,13 @@ const Admin = () => {
)
}
const handleToggle = () => {
setToggle(!toggle);
};
if (!user || !user.admin) {
return (
<div className="pageWrapper">
<h1>Unauthorized</h1>
</div>
)
}
return (
<div className="pageWrapper">

View file

@ -13,10 +13,6 @@ const Dashboard = () => {
let [loading, setLoading] = useState(true);
let [dashboardData, setDashboardData] = useState({});
if (!user) {
history.push("/login");
}
useEffect(() => {
if (!user) {
return
@ -28,14 +24,22 @@ const Dashboard = () => {
})
}, [user])
if (loading) {
return (
<div className="pageWrapper">
<ScaleLoader color="#6AD7E5" />
</div>
)
}
return (
<div className="pageWrapper">
<h1>
Dashboard!
{user.username}'s Dashboard!
</h1>
{loading
? <ScaleLoader color="#6AD7E5" size={60} />
: <ScrobbleTable data={dashboardData} />
: <ScrobbleTable data={dashboardData.items} />
}
</div>
);

View file

@ -1,25 +1,59 @@
import React, { useContext } from 'react';
import React, { useState, useEffect } from 'react';
import '../App.css';
import './Dashboard.css';
import { useHistory } from "react-router";
import AuthContext from '../Contexts/AuthContext';
import './Profile.css';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { getProfile } from '../Api/index'
import ScrobbleTable from '../Components/ScrobbleTable'
const Profile = () => {
const history = useHistory();
const { user } = useContext(AuthContext);
const Profile = (route) => {
const [loading, setLoading] = useState(true);
const [profile, setProfile] = useState({});
if (!user) {
history.push("/login");
let username = false;
if (route && route.match && route.match.params && route.match.params.uuid) {
username = route.match.params.uuid
}
useEffect(() => {
if (!username) {
return false;
}
getProfile(username)
.then(data => {
setProfile(data);
console.log(data)
setLoading(false);
})
}, [username])
if (loading) {
return (
<div className="pageWrapper">
<ScaleLoader color="#6AD7E5" />
</div>
)
}
if (!username || Object.keys(profile).length === 0) {
return (
<div className="pageWrapper">
Unable to fetch user
</div>
)
}
return (
<div className="pageWrapper">
<h1>
Welcome {user.username}!
{profile.username}'s Profile
</h1>
<div className="profileBody">
Last 10 scrobbles...<br/>
<ScrobbleTable data={profile.scrobbles}/>
</div>
</div>
);
}
export default Profile;

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

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

53
web/src/Pages/User.js Normal file
View file

@ -0,0 +1,53 @@
import React, { useContext, useState, useEffect } from 'react';
import '../App.css';
import './User.css';
import { useHistory } from "react-router";
import AuthContext from '../Contexts/AuthContext';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { getUser } from '../Api/index'
const User = () => {
const history = useHistory();
const { user } = useContext(AuthContext);
const [loading, setLoading] = useState(true);
const [userdata, setUserdata] = useState({});
useEffect(() => {
if (!user) {
return
}
getUser()
.then(data => {
setUserdata(data);
setLoading(false);
})
}, [user])
if (loading) {
return (
<div className="pageWrapper">
<ScaleLoader color="#6AD7E5" />
</div>
)
}
if (!user) {
history.push("/login")
}
return (
<div className="pageWrapper">
<h1>
Welcome {userdata.username}
</h1>
<p className="userBody">
Created At: {userdata.created_at}<br/>
Email: {userdata.email}<br/>
Verified: {userdata.verified ? '✓' : '✖'}
</p>
</div>
);
}
export default User;

View file

@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { HashRouter } from 'react-router-dom';
import { BrowserRouter } from 'react-router-dom';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.min.css';
@ -11,7 +11,7 @@ import AuthContextProvider from './Contexts/AuthContextProvider';
ReactDOM.render(
<AuthContextProvider>
<HashRouter>
<BrowserRouter>
<ToastContainer
position="bottom-right"
autoClose={5000}
@ -24,7 +24,7 @@ ReactDOM.render(
pauseOnHover
/>
<App />
</HashRouter>
</BrowserRouter>
</AuthContextProvider>,
document.getElementById('root')
);