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

View File

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

View File

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

View File

@ -5,6 +5,7 @@
padding: 1rem 0;
z-index: 1000;
border-radius: 0;
line-height: 1.95;
}
.sidebar-menu {

View File

@ -82,10 +82,6 @@ h1, h2, h3, h4, h5, h6 {
margin: 0.35em 0 0.35em;
}
body{
line-height: 1.95;
}
.pageTitle{
margin: 4px;
}

View File

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

View File

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

View File

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

View File

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

View File

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