a whole load of crap

This commit is contained in:
Erik 2022-11-27 23:21:25 +02:00
parent 2dfb996cde
commit f1a61067c1
Signed by: Navy.gif
GPG Key ID: 811EC0CD80E7E5FB
10 changed files with 140 additions and 82 deletions

View File

@ -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",

View File

@ -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>

View File

@ -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}

View File

@ -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 {

View File

@ -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;
} }

View File

@ -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>

View File

@ -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;

View File

@ -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>;
}; };

View File

@ -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)}`;
}; };

View File

@ -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: