a whole load of crap
This commit is contained in:
parent
2dfb996cde
commit
f1a61067c1
@ -12,12 +12,12 @@
|
|||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.27.0",
|
|
||||||
"eslint-plugin-react": "^7.31.10",
|
|
||||||
"http-proxy-middleware": "^2.0.6",
|
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^13.5.0"
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"eslint": "^8.27.0",
|
||||||
|
"eslint-plugin-react": "^7.31.10",
|
||||||
|
"http-proxy-middleware": "^2.0.6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
20
src/App.js
20
src/App.js
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { BrowserRouter, Navigate, Route, Routes} from 'react-router-dom';
|
import { Navigate, Route, Routes, useNavigate} from 'react-router-dom';
|
||||||
|
|
||||||
import './css/App.css';
|
import './css/App.css';
|
||||||
import Home from './pages/Home';
|
import Home from './pages/Home';
|
||||||
@ -8,7 +8,7 @@ import Sidebar, { SidebarMenu } from './components/Sidebar';
|
|||||||
import UserControls from './components/UserControls';
|
import UserControls from './components/UserControls';
|
||||||
import { useLoginContext } from './structures/UserContext';
|
import { useLoginContext } from './structures/UserContext';
|
||||||
import Login from './pages/Login';
|
import Login from './pages/Login';
|
||||||
import { fetchUser } from './util/Util';
|
import { get, setSession, setSettings } from './util/Util';
|
||||||
import PrivateRoute from './structures/PrivateRoute';
|
import PrivateRoute from './structures/PrivateRoute';
|
||||||
import { UnauthedRoute } from './structures/UnauthedRoute';
|
import { UnauthedRoute } from './structures/UnauthedRoute';
|
||||||
import Users from './pages/Users';
|
import Users from './pages/Users';
|
||||||
@ -20,12 +20,21 @@ function App() {
|
|||||||
|
|
||||||
const [user, updateUser] = useLoginContext();
|
const [user, updateUser] = useLoginContext();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
await fetchUser();
|
|
||||||
updateUser();
|
const settings = await get('/api/settings');
|
||||||
|
setSettings(settings.data);
|
||||||
|
|
||||||
|
const result = await get('/api/user');
|
||||||
|
if (result.status === 200) {
|
||||||
|
setSession(result.data);
|
||||||
|
updateUser();
|
||||||
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
if (result.data?.twoFactor) return navigate('/login/verify');
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -42,8 +51,6 @@ function App() {
|
|||||||
|
|
||||||
<div className='background'>
|
<div className='background'>
|
||||||
|
|
||||||
<BrowserRouter>
|
|
||||||
|
|
||||||
{user ?
|
{user ?
|
||||||
<div>
|
<div>
|
||||||
<header className="card">
|
<header className="card">
|
||||||
@ -93,7 +100,6 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</BrowserRouter>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, {} from 'react';
|
||||||
import '../css/components/Sidebar.css';
|
import '../css/components/Sidebar.css';
|
||||||
import NavLink from '../structures/NavLink';
|
import NavLink from '../structures/NavLink';
|
||||||
import logoImg from '../img/logo.png';
|
import logoImg from '../img/logo.png';
|
||||||
@ -14,7 +14,7 @@ const Sidebar = ({children}) => {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const expandMenu = (event) => {
|
const toggleMenu = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.target.parentElement.classList.toggle("open");
|
event.target.parentElement.classList.toggle("open");
|
||||||
};
|
};
|
||||||
@ -54,9 +54,10 @@ const SidebarMenu = ({menuItems = [], children}) => {
|
|||||||
if(relative) to = `${menuItem.to}${to}`;
|
if(relative) to = `${menuItem.to}${to}`;
|
||||||
return <NavLink className='sidebar-menu-item sidebar-menu-child-item' onClick={closeMobileMenu} activeClassName='active' to={to} key={label}>{label}</NavLink>;
|
return <NavLink className='sidebar-menu-item sidebar-menu-child-item' onClick={closeMobileMenu} activeClassName='active' to={to} key={label}>{label}</NavLink>;
|
||||||
});
|
});
|
||||||
|
|
||||||
elements.push(<div key={label} className='parent-menu'>
|
elements.push(<div key={label} className='parent-menu'>
|
||||||
<NavLink className={`sidebar-menu-item ${subElements?.length ? 'has-children' : ''}`} onClick={closeMobileMenu} activeClassName='active' {...rest} key={label}>
|
<NavLink className={`sidebar-menu-item ${subElements?.length ? 'has-children' : ''}`} onClick={closeMobileMenu} activeClassName='active open' {...rest} key={label}>
|
||||||
{label}{subElements && <i className="sidebar-menu-item-carrot" onClick={expandMenu}></i>}
|
{label}{subElements && <i className="sidebar-menu-item-carrot" onClick={toggleMenu}></i>}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{subElements && <div className='sidebar-menu-child-wrapper'>
|
{subElements && <div className='sidebar-menu-child-wrapper'>
|
||||||
{subElements}
|
{subElements}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
line-height: 1.95;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-menu {
|
.sidebar-menu {
|
||||||
|
@ -82,10 +82,6 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
margin: 0.35em 0 0.35em;
|
margin: 0.35em 0 0.35em;
|
||||||
}
|
}
|
||||||
|
|
||||||
body{
|
|
||||||
line-height: 1.95;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pageTitle{
|
.pageTitle{
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,14 @@ import './css/index.css';
|
|||||||
import App from './App';
|
import App from './App';
|
||||||
// import reportWebVitals from './reportWebVitals';
|
// import reportWebVitals from './reportWebVitals';
|
||||||
import { UserContext } from './structures/UserContext';
|
import { UserContext } from './structures/UserContext';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
root.render(<React.StrictMode>
|
root.render(<React.StrictMode>
|
||||||
<UserContext>
|
<UserContext>
|
||||||
<App />
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
</UserContext>
|
</UserContext>
|
||||||
</React.StrictMode>);
|
</React.StrictMode>);
|
||||||
// root.render(<UserContext>
|
// root.render(<UserContext>
|
||||||
|
@ -2,12 +2,13 @@ import React, { useRef, useState } from "react";
|
|||||||
import { Route, Routes } from "react-router";
|
import { Route, Routes } from "react-router";
|
||||||
import '../css/pages/Home.css';
|
import '../css/pages/Home.css';
|
||||||
import { useLoginContext } from "../structures/UserContext";
|
import { useLoginContext } from "../structures/UserContext";
|
||||||
import { get, post } from "../util/Util";
|
import ErrorBoundary from "../util/ErrorBoundary";
|
||||||
|
import { capitalise, get, post } from "../util/Util";
|
||||||
|
|
||||||
const Profile = () => {
|
const TwoFactorControls = ({ user }) => {
|
||||||
|
|
||||||
const [user] = useLoginContext();
|
|
||||||
const [twoFactorError, set2FAError] = useState(null);
|
const [twoFactorError, set2FAError] = useState(null);
|
||||||
|
const [displayInput, setDisplayInput] = useState(false);
|
||||||
|
|
||||||
const [qr, setQr] = useState(null);
|
const [qr, setQr] = useState(null);
|
||||||
const displayQr = async () => {
|
const displayQr = async () => {
|
||||||
@ -16,36 +17,89 @@ const Profile = () => {
|
|||||||
setQr(response.message);
|
setQr(response.message);
|
||||||
};
|
};
|
||||||
|
|
||||||
const authCodeRef = useRef();
|
const codeRef = useRef();
|
||||||
const disable2FA = async () => {
|
const disable2FA = async (event) => {
|
||||||
const code = authCodeRef.current.value;
|
event.preventDefault();
|
||||||
const response = await post('/api/user/2fa/disable', {code});
|
const code = codeRef.current.value;
|
||||||
if(response.status !== 200) return set2FAError(response.message);
|
if (!code) return;
|
||||||
|
const response = await post('/api/user/2fa/disable', { code });
|
||||||
|
if (response.status !== 200) return set2FAError(response.message);
|
||||||
};
|
};
|
||||||
|
|
||||||
const codeRef = useRef();
|
const submitCode = async (event) => {
|
||||||
const submitCode = async () => {
|
event.preventDefault();
|
||||||
|
|
||||||
const code = codeRef.current.value;
|
const code = codeRef.current.value;
|
||||||
if (!code) return;
|
if (!code) return;
|
||||||
const response = await post('/api/user/2fa/verify', { code });
|
const response = await post('/api/user/2fa/verify', { code });
|
||||||
if (response.status !== 200) return set2FAError(response.message);
|
if (response.status !== 200) return set2FAError(response.message);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let inner = <div>
|
||||||
|
{qr ?
|
||||||
|
<div>
|
||||||
|
<img src={qr} />
|
||||||
|
<form>
|
||||||
|
<input placeholder='Authenticator code' ref={codeRef} type='password' />
|
||||||
|
<button onClick={submitCode} className="button success">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>:
|
||||||
|
<button onClick={displayQr} className="button primary">Enable 2FA</button>}
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
|
||||||
|
if (user.twoFactor) inner = <div>
|
||||||
|
{displayInput ?
|
||||||
|
<form>
|
||||||
|
<input placeholder='Authenticator code to disable' ref={codeRef} type='password' />
|
||||||
|
<button className="button error" onClick={disable2FA}>Submit</button>
|
||||||
|
</form>
|
||||||
|
: <button onClick={() => setDisplayInput(true)} className="button error">Disable 2FA</button>}
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
{twoFactorError && <p>{twoFactorError}</p>}
|
||||||
|
{inner}
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExternalProfile = ({ profile }) => {
|
||||||
|
return <div>
|
||||||
|
<b>{capitalise(profile.provider)}</b>
|
||||||
|
<p className="m-0">Username: {profile.username}</p>
|
||||||
|
<p className="m-0">ID: {profile.id}</p>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ThirdPartyConnections = ({user}) => {
|
||||||
|
|
||||||
|
const {externalProfiles} = user;
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
{externalProfiles.map(profile => <ExternalProfile key={profile.id} profile={profile} />)}
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const Profile = () => {
|
||||||
|
|
||||||
|
const [user] = useLoginContext();
|
||||||
|
|
||||||
return <div className="row">
|
return <div className="row">
|
||||||
|
|
||||||
<div className="col-6-lg col-12">
|
<div className="col-6-lg col-12">
|
||||||
|
|
||||||
<h3>Profile</h3>
|
<h3>Profile</h3>
|
||||||
|
|
||||||
<p><b>Profile Picture</b></p>
|
<p><b>Profile Picture</b></p>
|
||||||
<img width={256} height={256} src={`/api/users/${user.id}/avatar`} />
|
<img width={256} height={256} src={`/api/users/${user.id}/avatar`} />
|
||||||
<form>
|
<form>
|
||||||
<input type='file' accept='image/*' />
|
<input type='file' accept='image/*' />
|
||||||
<button>Submit</button>
|
<button className="button primary">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<h4>3rd party connections</h4>
|
||||||
|
<ThirdPartyConnections user={user} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-6-lg col-12">
|
<div className="col-6-lg col-12">
|
||||||
@ -67,25 +121,9 @@ const Profile = () => {
|
|||||||
|
|
||||||
<button className="button primary">Save</button>
|
<button className="button primary">Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p><b>Two Factor</b></p>
|
<p className="mb-0 mt-4"><b>Two Factor:</b> {user.twoFactor ? 'enabled' : 'disabled'}</p>
|
||||||
{twoFactorError && <p>{twoFactorError}</p>}
|
<TwoFactorControls user={user} />
|
||||||
{!user.twoFactor ?
|
|
||||||
<div>
|
|
||||||
{qr ?
|
|
||||||
<div>
|
|
||||||
<img src={qr} />
|
|
||||||
<form>
|
|
||||||
<input ref={codeRef} autoComplete="off" type='password' />
|
|
||||||
<button onClick={submitCode} className="button success">Submit</button>
|
|
||||||
</form>
|
|
||||||
</div> :
|
|
||||||
<button onClick={displayQr} className="button primary">Enable 2FA</button>}
|
|
||||||
</div> :
|
|
||||||
<div>
|
|
||||||
<button onClick={disable2FA} className="button error">Disable 2FA</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
@ -100,9 +138,11 @@ const Main = () => {
|
|||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
|
|
||||||
return <Routes>
|
return <ErrorBoundary>
|
||||||
<Route path="/" element={<Main />} />
|
<Routes>
|
||||||
<Route path="/profile" element={<Profile />} />
|
<Route path="/" element={<Main />} />
|
||||||
</Routes>;
|
<Route path="/profile" element={<Profile />} />
|
||||||
|
</Routes>
|
||||||
|
</ErrorBoundary>;
|
||||||
};
|
};
|
||||||
export default Home;
|
export default Home;
|
@ -4,15 +4,7 @@ import '../css/pages/Login.css';
|
|||||||
import logoImg from '../img/logo.png';
|
import logoImg from '../img/logo.png';
|
||||||
import { useLoginContext } from "../structures/UserContext";
|
import { useLoginContext } from "../structures/UserContext";
|
||||||
import LoadingBar from 'react-top-loading-bar';
|
import LoadingBar from 'react-top-loading-bar';
|
||||||
import { fetchUser, post } from "../util/Util";
|
import { fetchUser, getSettings, post } from "../util/Util";
|
||||||
|
|
||||||
const loginMethods = [
|
|
||||||
{
|
|
||||||
name: 'Discord',
|
|
||||||
img: 'https://assets-global.website-files.com/6257adef93867e50d84d30e2/636e0a69f118df70ad7828d4_icon_clyde_blurple_RGB.svg'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
const CredentialFields = () => {
|
const CredentialFields = () => {
|
||||||
|
|
||||||
@ -21,6 +13,8 @@ const CredentialFields = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
|
||||||
|
const loginMethods = getSettings()?.OAuthProviders || [];
|
||||||
|
|
||||||
const loginClick = async (event) => {
|
const loginClick = async (event) => {
|
||||||
ref.current.continuousStart();
|
ref.current.continuousStart();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -71,15 +65,18 @@ const CredentialFields = () => {
|
|||||||
<div className="card is-center dir-column">
|
<div className="card is-center dir-column">
|
||||||
<b>Alternate login methods</b>
|
<b>Alternate login methods</b>
|
||||||
<div className="methodsWrapper is-center flex-wrap">
|
<div className="methodsWrapper is-center flex-wrap">
|
||||||
{loginMethods.map(method => <img
|
{loginMethods.map(method => <div
|
||||||
crossOrigin="anonymous"
|
|
||||||
className='third-party-login'
|
className='third-party-login'
|
||||||
key={method.name}
|
key={method.name}
|
||||||
alt={method.name}
|
onClick={() => { window.location.pathname = `/api/login/${method.name}`; }}>
|
||||||
src={method.img}
|
<img
|
||||||
width={48} height={48}
|
crossOrigin="anonymous"
|
||||||
onClick={() => { window.location.pathname = `/api/login/${method.name}`; }}
|
alt={method.name}
|
||||||
/>)}
|
src={method.icon}
|
||||||
|
width={48} height={48}
|
||||||
|
/>
|
||||||
|
<p><b>{method.name}</b></p>
|
||||||
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
@ -92,7 +89,8 @@ const TwoFactor = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
|
||||||
const twoFactorClick = async () => {
|
const twoFactorClick = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
const code = document.getElementById('2faCode').value;
|
const code = document.getElementById('2faCode').value;
|
||||||
if (!code) return;
|
if (!code) return;
|
||||||
ref.current.continuousStart();
|
ref.current.continuousStart();
|
||||||
@ -115,8 +113,10 @@ const TwoFactor = () => {
|
|||||||
<div className="card mb-3 is-center dir-column">
|
<div className="card mb-3 is-center dir-column">
|
||||||
<h2>Verification Code</h2>
|
<h2>Verification Code</h2>
|
||||||
{fail ? <p className="mt-0">Invalid code</p> : null}
|
{fail ? <p className="mt-0">Invalid code</p> : null}
|
||||||
<input autoComplete='off' placeholder='Your 2FA code...' required id='2faCode' type='password' />
|
<form>
|
||||||
<button className="button primary m-3" onClick={twoFactorClick}>Enter</button>
|
<input autoComplete='off' placeholder='Your 2FA code...' required id='2faCode' type='password' />
|
||||||
|
<button className="button primary m-3" onClick={twoFactorClick}>Enter</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
@ -4,10 +4,6 @@ export const getUser = () => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getToken = () => {
|
|
||||||
return getUser()?.token || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const clearSession = () => {
|
export const clearSession = () => {
|
||||||
sessionStorage.removeItem('user');
|
sessionStorage.removeItem('user');
|
||||||
};
|
};
|
||||||
@ -25,6 +21,16 @@ export const fetchUser = async (force = false) => {
|
|||||||
setSession(result.data);
|
setSession(result.data);
|
||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setSettings = (settings) => {
|
||||||
|
if(settings) sessionStorage.setItem('settings', JSON.stringify(settings));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSettings = () => {
|
||||||
|
const data = sessionStorage.getItem('settings');
|
||||||
|
if (data) return JSON.parse(data);
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,4 +72,9 @@ export const get = async (url, params) => {
|
|||||||
.join('&');
|
.join('&');
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
return parseResponse(response);
|
return parseResponse(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const capitalise = (str) => {
|
||||||
|
const first = str[0].toUpperCase();
|
||||||
|
return `${first}${str.substring(1)}`;
|
||||||
};
|
};
|
@ -7498,7 +7498,7 @@ react-scripts@5.0.1:
|
|||||||
|
|
||||||
react-top-loading-bar@^2.3.1:
|
react-top-loading-bar@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-top-loading-bar/-/react-top-loading-bar-2.3.1.tgz#d727eb6aaa412eae52a990e5de9f33e9136ac714"
|
resolved "https://registry.corgi.wtf/react-top-loading-bar/-/react-top-loading-bar-2.3.1.tgz#d727eb6aaa412eae52a990e5de9f33e9136ac714"
|
||||||
integrity sha512-rQk2Nm+TOBrM1C4E3e6KwT65iXyRSgBHjCkr2FNja1S51WaPulRA5nKj/xazuQ3x89wDDdGsrqkqy0RBIfd0xg==
|
integrity sha512-rQk2Nm+TOBrM1C4E3e6KwT65iXyRSgBHjCkr2FNja1S51WaPulRA5nKj/xazuQ3x89wDDdGsrqkqy0RBIfd0xg==
|
||||||
|
|
||||||
react@^18.2.0:
|
react@^18.2.0:
|
||||||
|
Loading…
Reference in New Issue
Block a user