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