Linter pass
This commit is contained in:
parent
2da5c82360
commit
fd4d72eb21
@ -28,7 +28,8 @@
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"eject": "react-scripts eject",
|
||||
"lint": "eslint src/ --fix"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
18
src/App.tsx
18
src/App.tsx
@ -17,25 +17,30 @@ import Register from './pages/Register';
|
||||
import { ClientSettings } from './@types/Other';
|
||||
import { User } from './@types/ApiStructures';
|
||||
|
||||
function App() {
|
||||
function App()
|
||||
{
|
||||
|
||||
const [user, updateUser] = useLoginContext();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
|
||||
const settings = await get('/api/settings');
|
||||
setSettings(settings.data as ClientSettings);
|
||||
|
||||
const result = await get('/api/user');
|
||||
if (result.status === 200) {
|
||||
if (result.status === 200)
|
||||
{
|
||||
setSession(result.data as User);
|
||||
updateUser();
|
||||
}
|
||||
setLoading(false);
|
||||
if (result.data?.twoFactor) return navigate('/login/verify');
|
||||
if (result.data?.twoFactor)
|
||||
return navigate('/login/verify');
|
||||
})();
|
||||
}, []);
|
||||
|
||||
@ -55,7 +60,8 @@ function App() {
|
||||
}
|
||||
];
|
||||
|
||||
if (loading) return null;
|
||||
if (loading)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<div className='app'>
|
||||
|
@ -3,20 +3,25 @@ import ClickDetector from "../util/ClickDetector";
|
||||
import { DropdownBaseProps, DropdownItemProps } from "../@types/Components";
|
||||
import '../css/components/InputElements.css';
|
||||
|
||||
export const FileSelector = ({ cb }: { cb: (file: File) => void }) => {
|
||||
export const FileSelector = ({ cb }: { cb: (file: File) => void }) =>
|
||||
{
|
||||
|
||||
if (!cb) throw new Error('Missing callback');
|
||||
if (!cb)
|
||||
throw new Error('Missing callback');
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
|
||||
const onDragOver: React.MouseEventHandler = (event) => {
|
||||
const onDragOver: React.MouseEventHandler = (event) =>
|
||||
{
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const onDrop: React.DragEventHandler = (event) => {
|
||||
const onDrop: React.DragEventHandler = (event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
const { dataTransfer } = event;
|
||||
if (!dataTransfer.files.length) return;
|
||||
if (!dataTransfer.files.length)
|
||||
return;
|
||||
|
||||
const [file] = dataTransfer.files;
|
||||
setFile(file);
|
||||
@ -28,8 +33,10 @@ export const FileSelector = ({ cb }: { cb: (file: File) => void }) => {
|
||||
or
|
||||
<span className="drop-title">Click to select a file</span>
|
||||
<p className="fileName m-0">{file ? `Selected: ${file.name}` : null}</p>
|
||||
<input onChange={(event) => {
|
||||
if (!event.target.files) return;
|
||||
<input onChange={(event) =>
|
||||
{
|
||||
if (!event.target.files)
|
||||
return;
|
||||
const [f] = event.target.files;
|
||||
setFile(f);
|
||||
cb(f);
|
||||
@ -46,7 +53,8 @@ export type InputElementProperties<T> = {
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
export const ToggleSwitch = ({ value, onChange, inputRef, children }: InputElementProperties<boolean>) => {
|
||||
export const ToggleSwitch = ({ value, onChange, inputRef, children }: InputElementProperties<boolean>) =>
|
||||
{
|
||||
return <label className="label-fix">
|
||||
<span className="check-box check-box-row mb-2">
|
||||
{children && <b>{children}</b>}
|
||||
@ -55,7 +63,8 @@ export const ToggleSwitch = ({ value, onChange, inputRef, children }: InputEleme
|
||||
</label>;
|
||||
};
|
||||
|
||||
export const VerticalToggleSwitch = ({ value, onChange, inputRef, children }: InputElementProperties<boolean>) => {
|
||||
export const VerticalToggleSwitch = ({ value, onChange, inputRef, children }: InputElementProperties<boolean>) =>
|
||||
{
|
||||
return <label className="label-fix">
|
||||
{children && <b>{children}</b>}
|
||||
<span className="check-box">
|
||||
@ -73,7 +82,8 @@ type StringInputProperties = {
|
||||
maxLength?: number,
|
||||
minLength?: number
|
||||
} & ManualInputProperties<string>
|
||||
export const StringInput = ({ value, onChange, inputRef, children, placeholder, onBlur, onKeyUp, minLength, maxLength }: StringInputProperties) => {
|
||||
export const StringInput = ({ value, onChange, inputRef, children, placeholder, onBlur, onKeyUp, minLength, maxLength }: StringInputProperties) =>
|
||||
{
|
||||
|
||||
const input = <input
|
||||
onBlur={onBlur}
|
||||
@ -100,11 +110,16 @@ export type NumberInputProperties = {
|
||||
step?: number
|
||||
} & ManualInputProperties<number>
|
||||
|
||||
export const NumberInput = ({ children, placeholder, inputRef, onChange, value, min, max, type, step }: NumberInputProperties) => {
|
||||
if (typeof step === 'undefined') {
|
||||
if (type === 'float') step = 0.1;
|
||||
else if (type === 'int') step = 1;
|
||||
else step = 1;
|
||||
export const NumberInput = ({ children, placeholder, inputRef, onChange, value, min, max, type, step }: NumberInputProperties) =>
|
||||
{
|
||||
if (typeof step === 'undefined')
|
||||
{
|
||||
if (type === 'float')
|
||||
step = 0.1;
|
||||
else if (type === 'int')
|
||||
step = 1;
|
||||
else
|
||||
step = 1;
|
||||
}
|
||||
|
||||
const input = <input
|
||||
@ -118,7 +133,8 @@ export const NumberInput = ({ children, placeholder, inputRef, onChange, value,
|
||||
step={step}
|
||||
/>;
|
||||
|
||||
if (children) return <label>
|
||||
if (children)
|
||||
return <label>
|
||||
<b>{children}</b>
|
||||
{input}
|
||||
</label>;
|
||||
@ -127,17 +143,20 @@ export const NumberInput = ({ children, placeholder, inputRef, onChange, value,
|
||||
};
|
||||
|
||||
export const ClickToEdit = ({ value, onUpdate, inputElement }:
|
||||
{ value: string, onUpdate?: (value: string) => void, inputElement?: React.ReactElement }) => {
|
||||
{ value: string, onUpdate?: (value: string) => void, inputElement?: React.ReactElement }) =>
|
||||
{
|
||||
const [editing, setEditing] = useState(false);
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
const onClick = () => {
|
||||
const onClick = () =>
|
||||
{
|
||||
setEditing(false);
|
||||
if (ref.current && onUpdate)
|
||||
onUpdate(ref.current.value);
|
||||
};
|
||||
|
||||
const input = inputElement ? inputElement : <input defaultValue={value} ref={ref} />;
|
||||
if (editing) return <span className='input-group'>
|
||||
if (editing)
|
||||
return <span className='input-group'>
|
||||
{input}
|
||||
<button className="button primary" onClick={onClick} >
|
||||
OK
|
||||
@ -146,21 +165,26 @@ export const ClickToEdit = ({ value, onUpdate, inputElement }:
|
||||
return <span onClick={() => setEditing(true)} className='mt-0 mb-1 clickable'>{value}</span>;
|
||||
};
|
||||
|
||||
const DropdownHeader = ({ children, className = '' }: DropdownBaseProps) => {
|
||||
const DropdownHeader = ({ children, className = '' }: DropdownBaseProps) =>
|
||||
{
|
||||
return <summary className={`clickable card is-vertical-align header p-2 ${className}`}>
|
||||
{children}
|
||||
</summary>;
|
||||
};
|
||||
|
||||
const DropdownItem = ({ children, type, selected, onClick }: DropdownItemProps) => {
|
||||
const DropdownItem = ({ children, type, selected, onClick }: DropdownItemProps) =>
|
||||
{
|
||||
let InnerElement = null;
|
||||
if (type === 'multi-select')
|
||||
InnerElement = <ToggleSwitch value={selected || false} onChange={onClick}>
|
||||
{children as string}
|
||||
</ToggleSwitch>;
|
||||
else InnerElement = <div onClick={(event) => {
|
||||
else
|
||||
InnerElement = <div onClick={(event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
if (onClick) onClick(event);
|
||||
if (onClick)
|
||||
onClick(event);
|
||||
}}>
|
||||
{children}
|
||||
</div>;
|
||||
@ -169,7 +193,8 @@ const DropdownItem = ({ children, type, selected, onClick }: DropdownItemProps)
|
||||
</div>;
|
||||
};
|
||||
|
||||
const DropdownItemList = ({ children, className = '' }: DropdownBaseProps) => {
|
||||
const DropdownItemList = ({ children, className = '' }: DropdownBaseProps) =>
|
||||
{
|
||||
return <div className={`card item-list ${className}`}>
|
||||
{children}
|
||||
</div>;
|
||||
@ -183,7 +208,8 @@ type DropdownProps = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const DropdownComp = ({ children, className = '' }: DropdownProps) => {
|
||||
const DropdownComp = ({ children, className = '' }: DropdownProps) =>
|
||||
{
|
||||
|
||||
if (!children)
|
||||
throw new Error('Missing children');
|
||||
@ -193,8 +219,10 @@ const DropdownComp = ({ children, className = '' }: DropdownProps) => {
|
||||
|
||||
const detailsRef = useRef<HTMLDetailsElement>(null);
|
||||
|
||||
return <ClickDetector callback={() => {
|
||||
if (detailsRef.current) detailsRef.current.open = false;
|
||||
return <ClickDetector callback={() =>
|
||||
{
|
||||
if (detailsRef.current)
|
||||
detailsRef.current.open = false;
|
||||
}}>
|
||||
<details ref={detailsRef} className={`dropdown w-100 ${className}`}>
|
||||
{children}
|
||||
|
@ -7,22 +7,27 @@ type PageButtonProps = {
|
||||
pages: number
|
||||
}
|
||||
|
||||
export const PageButtons = ({ setPage, page, pages }: PageButtonProps) => {
|
||||
export const PageButtons = ({ setPage, page, pages }: PageButtonProps) =>
|
||||
{
|
||||
return <div className='flex is-vertical-align is-center page-controls'>
|
||||
<button className="button dark" disabled={page === 1} onClick={() => {
|
||||
<button className="button dark" disabled={page === 1} onClick={() =>
|
||||
{
|
||||
setPage(page - 1 || 1);
|
||||
}}>Previous</button>
|
||||
<p>Page: {page} / {pages}</p>
|
||||
<button className="button dark" disabled={page === pages} onClick={() => {
|
||||
<button className="button dark" disabled={page === pages} onClick={() =>
|
||||
{
|
||||
if (page < pages)
|
||||
setPage(page + 1);
|
||||
}}>Next</button>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export const BackButton = () => {
|
||||
export const BackButton = () =>
|
||||
{
|
||||
const navigate = useNavigate();
|
||||
return <button className="button dark" onClick={() => {
|
||||
return <button className="button dark" onClick={() =>
|
||||
{
|
||||
navigate(-1);
|
||||
}}>Back</button>;
|
||||
};
|
@ -1,8 +1,10 @@
|
||||
import React from "react";
|
||||
import '../css/components/PageElements.css';
|
||||
|
||||
export const Popup = ({ display = false, children }: { display: boolean, children: React.ReactElement }) => {
|
||||
if (!display) return null;
|
||||
export const Popup = ({ display = false, children }: { display: boolean, children: React.ReactElement }) =>
|
||||
{
|
||||
if (!display)
|
||||
return null;
|
||||
return <div className='popup'>
|
||||
{children}
|
||||
</div>;
|
||||
|
@ -7,38 +7,47 @@ import { useLoginContext } from "../structures/UserContext";
|
||||
import { clearSession, post } from "../util/Util";
|
||||
import { SidebarMenuItem } from '../@types/Other';
|
||||
|
||||
const Sidebar = ({children}: {children?: React.ReactNode}) => {
|
||||
const Sidebar = ({children}: {children?: React.ReactNode}) =>
|
||||
{
|
||||
return <div className='sidebar card'>
|
||||
{children}
|
||||
</div>;
|
||||
};
|
||||
|
||||
const toggleMenu: React.MouseEventHandler = (event) => {
|
||||
const toggleMenu: React.MouseEventHandler = (event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
(event.target as HTMLElement).parentElement?.classList.toggle("open");
|
||||
};
|
||||
|
||||
const expandMobileMenu: React.MouseEventHandler = (event) => {
|
||||
const expandMobileMenu: React.MouseEventHandler = (event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
(event.target as HTMLElement).parentElement?.parentElement?.classList.toggle("show-menu");
|
||||
};
|
||||
|
||||
const closeMobileMenu: React.MouseEventHandler = (event) => {
|
||||
const closeMobileMenu: React.MouseEventHandler = (event) =>
|
||||
{
|
||||
const element = event.target as HTMLElement;
|
||||
if(element.classList.contains("sidebar-menu-item-carrot")) return;
|
||||
if(element.classList.contains("sidebar-menu-item-carrot"))
|
||||
return;
|
||||
(element.getRootNode() as ParentNode).querySelector(".sidebar")?.classList.remove("show-menu");
|
||||
};
|
||||
|
||||
// Nav menu
|
||||
const SidebarMenu = ({menuItems = [], children}: {menuItems: SidebarMenuItem[], children?: React.ReactNode}) => {
|
||||
const SidebarMenu = ({menuItems = [], children}: {menuItems: SidebarMenuItem[], children?: React.ReactNode}) =>
|
||||
{
|
||||
const [user, updateUser] = useLoginContext();
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!user) return null;
|
||||
if (!user)
|
||||
return null;
|
||||
|
||||
const logOut = async () => {
|
||||
const logOut = async () =>
|
||||
{
|
||||
const response = await post('/api/logout');
|
||||
if (response.status === 200) {
|
||||
if (response.status === 200)
|
||||
{
|
||||
clearSession();
|
||||
updateUser();
|
||||
navigate('/login');
|
||||
@ -46,12 +55,16 @@ const SidebarMenu = ({menuItems = [], children}: {menuItems: SidebarMenuItem[],
|
||||
};
|
||||
|
||||
const elements = [];
|
||||
for (const menuItem of menuItems) {
|
||||
for (const menuItem of menuItems)
|
||||
{
|
||||
const { label, items: subItems = [], ...rest } = menuItem;
|
||||
|
||||
let subElements = null;
|
||||
if (subItems.length) subElements = subItems.map(({ label, to, relative = true }) => {
|
||||
if(relative) to = `${menuItem.to}${to}`;
|
||||
if (subItems.length)
|
||||
subElements = subItems.map(({ label, to, relative = true }) =>
|
||||
{
|
||||
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>;
|
||||
});
|
||||
|
||||
|
@ -17,14 +17,16 @@ type TableProps = {
|
||||
itemKeys?: string[]
|
||||
}
|
||||
|
||||
export const TableListEntry = ({onClick, item, itemKeys}: TableEntry) => {
|
||||
export const TableListEntry = ({onClick, item, itemKeys}: TableEntry) =>
|
||||
{
|
||||
|
||||
return <tr onClick={onClick}>
|
||||
{itemKeys.map(key => <td key={key}>{item[key] as string}</td>)}
|
||||
</tr>;
|
||||
};
|
||||
|
||||
export const Table = ({headerItems, children, items, itemKeys}: TableProps) => {
|
||||
export const Table = ({headerItems, children, items, itemKeys}: TableProps) =>
|
||||
{
|
||||
|
||||
if (!headerItems)
|
||||
throw new Error(`Missing table headers`);
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import '../css/pages/Empty.css';
|
||||
|
||||
const TitledPage = ({title, children}: {title: string, children: React.ReactNode}) => {
|
||||
const TitledPage = ({title, children}: {title: string, children: React.ReactNode}) =>
|
||||
{
|
||||
|
||||
return <div className="page">
|
||||
<h2 className="pageTitle">{title}</h2>
|
||||
|
@ -5,25 +5,31 @@ import { useLoginContext } from "../structures/UserContext";
|
||||
import ClickDetector from "../util/ClickDetector";
|
||||
import { clearSession, post } from "../util/Util";
|
||||
|
||||
const UserControls = () => {
|
||||
const UserControls = () =>
|
||||
{
|
||||
|
||||
const [user, updateUser] = useLoginContext();
|
||||
const detailsRef = useRef<HTMLDetailsElement>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!user) return null;
|
||||
if (!user)
|
||||
return null;
|
||||
|
||||
const logOut = async () => {
|
||||
const logOut = async () =>
|
||||
{
|
||||
const response = await post('/api/logout');
|
||||
if (response.status === 200) {
|
||||
if (response.status === 200)
|
||||
{
|
||||
clearSession();
|
||||
updateUser();
|
||||
navigate('/login');
|
||||
}
|
||||
};
|
||||
|
||||
return <ClickDetector callback={() => {
|
||||
if (detailsRef.current) detailsRef.current.removeAttribute('open');
|
||||
return <ClickDetector callback={() =>
|
||||
{
|
||||
if (detailsRef.current)
|
||||
detailsRef.current.removeAttribute('open');
|
||||
}}>
|
||||
<details ref={detailsRef} className='dropdown user-controls'>
|
||||
<summary className="is-vertical-align">
|
||||
|
@ -8,10 +8,12 @@ import Users from "./admin/Users";
|
||||
import Flags from "./admin/Flags";
|
||||
|
||||
|
||||
const Main = () => {
|
||||
const Main = () =>
|
||||
{
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
|
||||
const submit: React.MouseEventHandler = (event) => {
|
||||
const submit: React.MouseEventHandler = (event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
console.log(file);
|
||||
};
|
||||
@ -36,7 +38,8 @@ const Main = () => {
|
||||
</div>;
|
||||
};
|
||||
|
||||
const Admin = () => {
|
||||
const Admin = () =>
|
||||
{
|
||||
|
||||
return <ErrorBoundary>
|
||||
<Routes>
|
||||
|
@ -5,14 +5,16 @@ import ErrorBoundary from "../util/ErrorBoundary";
|
||||
import Applications from "./home/Applications";
|
||||
import Profile from "./home/Profile";
|
||||
|
||||
const Main = () => {
|
||||
const Main = () =>
|
||||
{
|
||||
|
||||
return <div>
|
||||
What to put here? hmmm
|
||||
</div>;
|
||||
};
|
||||
|
||||
const Home = () => {
|
||||
const Home = () =>
|
||||
{
|
||||
|
||||
return <ErrorBoundary>
|
||||
<Routes>
|
||||
|
@ -7,7 +7,8 @@ import LoadingBar, {LoadingBarRef} from 'react-top-loading-bar';
|
||||
import { fetchUser, getSettings, post } from "../util/Util";
|
||||
import { OAuthProvider } from "../@types/Other";
|
||||
|
||||
const CredentialFields = () => {
|
||||
const CredentialFields = () =>
|
||||
{
|
||||
|
||||
const [, setUser] = useLoginContext();
|
||||
const [fail, setFail] = useState(false);
|
||||
@ -18,24 +19,28 @@ const CredentialFields = () => {
|
||||
|
||||
const loginMethods = getSettings()?.OAuthProviders || [];
|
||||
|
||||
const loginClick: React.MouseEventHandler = async (event: React.MouseEvent) => {
|
||||
const loginClick: React.MouseEventHandler = async (event: React.MouseEvent) =>
|
||||
{
|
||||
ref.current?.continuousStart();
|
||||
event.preventDefault();
|
||||
// const username = (document.getElementById('username') as HTMLInputElement)?.value;
|
||||
// const password = (document.getElementById('password') as HTMLInputElement)?.value;
|
||||
const username = usernameRef.current?.value;
|
||||
const password = passwordRef.current?.value;
|
||||
if (!username?.length || !password?.length) return;
|
||||
if (!username?.length || !password?.length)
|
||||
return;
|
||||
|
||||
const result = await post('/api/login', { username, password });
|
||||
|
||||
if (![200, 302].includes(result.status)) {
|
||||
if (![200, 302].includes(result.status))
|
||||
{
|
||||
ref.current?.complete();
|
||||
setFail(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.data?.twoFactor) {
|
||||
if (!result.data?.twoFactor)
|
||||
{
|
||||
setUser(await fetchUser());
|
||||
ref.current?.complete();
|
||||
return navigate('/home');
|
||||
@ -73,7 +78,10 @@ const CredentialFields = () => {
|
||||
{loginMethods.map((method: OAuthProvider) => <div
|
||||
className='third-party-login'
|
||||
key={method.name}
|
||||
onClick={() => { window.location.pathname = `/api/login/${method.name}`; }}>
|
||||
onClick={() =>
|
||||
{
|
||||
window.location.pathname = `/api/login/${method.name}`;
|
||||
}}>
|
||||
<img
|
||||
crossOrigin="anonymous"
|
||||
alt={method.name}
|
||||
@ -87,7 +95,8 @@ const CredentialFields = () => {
|
||||
</div>;
|
||||
};
|
||||
|
||||
const TwoFactor = () => {
|
||||
const TwoFactor = () =>
|
||||
{
|
||||
|
||||
const [fail, setFail] = useState(false);
|
||||
const [, setUser] = useLoginContext();
|
||||
@ -95,14 +104,17 @@ const TwoFactor = () => {
|
||||
const mfaCode = useRef<HTMLInputElement>(null);
|
||||
const ref = useRef<LoadingBarRef>(null);
|
||||
|
||||
const twoFactorClick: React.MouseEventHandler = async (event) => {
|
||||
const twoFactorClick: React.MouseEventHandler = async (event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
// const code = document.getElementById('2faCode').value;
|
||||
const code = mfaCode.current?.value;
|
||||
if (!code) return;
|
||||
if (!code)
|
||||
return;
|
||||
ref.current?.continuousStart();
|
||||
const result = await post('/api/login/verify', { code });
|
||||
if (result.status === 200) {
|
||||
if (result.status === 200)
|
||||
{
|
||||
setUser(await fetchUser());
|
||||
ref.current?.complete();
|
||||
return navigate('/home');
|
||||
@ -128,7 +140,8 @@ const TwoFactor = () => {
|
||||
</div>;
|
||||
};
|
||||
|
||||
const Login = () => {
|
||||
const Login = () =>
|
||||
{
|
||||
|
||||
return <div className="row is-center is-full-screen is-marginless">
|
||||
<div className='loginWrapper col-6 col-6-md col-3-lg'>
|
||||
|
@ -5,7 +5,8 @@ import '../css/pages/Register.css';
|
||||
import logoImg from '../img/logo.png';
|
||||
import LoadingBar, {LoadingBarRef} from 'react-top-loading-bar';
|
||||
|
||||
const Register = () => {
|
||||
const Register = () =>
|
||||
{
|
||||
|
||||
const [error, setError] = useState<string>();
|
||||
const [params] = useSearchParams();
|
||||
@ -17,17 +18,20 @@ const Register = () => {
|
||||
document.body.classList.add('bg-triangles');
|
||||
const code = params.get('code');
|
||||
|
||||
const submit: React.MouseEventHandler = async (event) => {
|
||||
const submit: React.MouseEventHandler = async (event) =>
|
||||
{
|
||||
ref.current?.continuousStart();
|
||||
event.preventDefault();
|
||||
const username = usernameRef.current?.value;
|
||||
const password = passwordRef.current?.value;
|
||||
if (!username?.length || !password?.length) {
|
||||
if (!username?.length || !password?.length)
|
||||
{
|
||||
ref.current?.complete();
|
||||
return;
|
||||
}
|
||||
const response = await post('/api/register', { username, password, code });
|
||||
if (response.status !== 200) {
|
||||
if (response.status !== 200)
|
||||
{
|
||||
ref.current?.complete();
|
||||
return setError(response.message as string || 'unknown error');
|
||||
}
|
||||
|
@ -9,12 +9,14 @@ import { get, patch, post } from "../../util/Util";
|
||||
import { Permissions as Perms, Role as R } from "../../@types/ApiStructures";
|
||||
import { ToggleSwitch } from "../../components/InputElements";
|
||||
|
||||
const Role = ({ role }: {role: R}) => {
|
||||
const Role = ({ role }: {role: R}) =>
|
||||
{
|
||||
|
||||
// const perms = { ...role.permissions };
|
||||
const [perms, updatePerms] = useState<Perms>(role.permissions);
|
||||
|
||||
const commitPerms = async () => {
|
||||
const commitPerms = async () =>
|
||||
{
|
||||
await patch(`/api/roles/${role.id}`, perms);
|
||||
};
|
||||
|
||||
@ -57,21 +59,25 @@ const Role = ({ role }: {role: R}) => {
|
||||
</div>;
|
||||
};
|
||||
|
||||
const CreateRoleField = ({ numRoles, className, addRole }: { numRoles: number, className?: string, addRole: (role: R) => void }) => {
|
||||
const CreateRoleField = ({ numRoles, className, addRole }: { numRoles: number, className?: string, addRole: (role: R) => void }) =>
|
||||
{
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const nameRef = useRef<HTMLInputElement>(null);
|
||||
const positionRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const createRole: React.MouseEventHandler<HTMLButtonElement> = async (event) => {
|
||||
const createRole: React.MouseEventHandler<HTMLButtonElement> = async (event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
|
||||
if (!nameRef.current || !positionRef.current) return setError('Must supply name and position');
|
||||
if (!nameRef.current || !positionRef.current)
|
||||
return setError('Must supply name and position');
|
||||
const name = nameRef.current.value;
|
||||
const position = parseInt(positionRef.current.value);
|
||||
|
||||
const response = await post('/api/roles', { name, position });
|
||||
if (!response.success) return setError(response.message || 'Unknown error');
|
||||
if (!response.success)
|
||||
return setError(response.message || 'Unknown error');
|
||||
addRole(response.data as R);
|
||||
|
||||
};
|
||||
@ -94,7 +100,8 @@ const CreateRoleField = ({ numRoles, className, addRole }: { numRoles: number, c
|
||||
|
||||
};
|
||||
|
||||
const Roles = () => {
|
||||
const Roles = () =>
|
||||
{
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [roles, setRoles] = useState<R[]>([]);
|
||||
@ -102,35 +109,44 @@ const Roles = () => {
|
||||
const [pages, setPages] = useState(1);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const result = await get('/api/roles', { page });
|
||||
if (result.success && result.data) {
|
||||
if (result.success && result.data)
|
||||
{
|
||||
setError(null);
|
||||
setRoles(result.data.roles as R[]);
|
||||
setPages(result.data.pages);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
setError(result.message || 'Unknown error');
|
||||
}
|
||||
setLoading(false);
|
||||
})();
|
||||
}, [page]);
|
||||
|
||||
const RoleWrapper = () => {
|
||||
const RoleWrapper = () =>
|
||||
{
|
||||
const { id } = useParams();
|
||||
const role = roles.find(r => r.id === id);
|
||||
if(!role) return <p>Unknown role</p>;
|
||||
if(!role)
|
||||
return <p>Unknown role</p>;
|
||||
return <Role role={role} />;
|
||||
};
|
||||
|
||||
const navigate = useNavigate();
|
||||
const RoleListWrapper = () => {
|
||||
const RoleListWrapper = () =>
|
||||
{
|
||||
return <div className="role-list-wrapper row">
|
||||
<div className={`col-6-lg col-12 ld ${loading && 'loading'}`}>
|
||||
<h4>All Roles</h4>
|
||||
<Table headerItems={['Name', 'ID']}>
|
||||
{roles.map(role => <TableListEntry
|
||||
onClick={() => {
|
||||
onClick={() =>
|
||||
{
|
||||
navigate(role.id);
|
||||
}}
|
||||
key={role.id}
|
||||
|
@ -17,21 +17,25 @@ const SelectedUserContext = React.createContext<{
|
||||
updateUser:(user: PartialUser) => void
|
||||
}>({
|
||||
user: null,
|
||||
updateUser: () => { /** */ }
|
||||
updateUser: () =>
|
||||
{ /** */ }
|
||||
});
|
||||
const Context = ({ children }: { children: React.ReactNode }) => {
|
||||
const Context = ({ children }: { children: React.ReactNode }) =>
|
||||
{
|
||||
const [user, updateUser] = useState<PartialUser | null>(null);
|
||||
return <SelectedUserContext.Provider value={{ user, updateUser }}>
|
||||
{children}
|
||||
</SelectedUserContext.Provider>;
|
||||
};
|
||||
|
||||
const ApplicationList = ({ apps }: { apps: App[] }) => {
|
||||
const ApplicationList = ({ apps }: { apps: App[] }) =>
|
||||
{
|
||||
|
||||
const navigate = useNavigate();
|
||||
return <Table headerItems={['Name', 'ID']}>
|
||||
{apps.map(app => <TableListEntry
|
||||
onClick={() => {
|
||||
onClick={() =>
|
||||
{
|
||||
navigate(`applications/${app.id}`);
|
||||
}}
|
||||
key={app.id}
|
||||
@ -42,9 +46,11 @@ const ApplicationList = ({ apps }: { apps: App[] }) => {
|
||||
|
||||
};
|
||||
|
||||
const Application = ({ app }: { app: App }) => {
|
||||
const Application = ({ app }: { app: App }) =>
|
||||
{
|
||||
|
||||
const commitPerms = async () => {
|
||||
const commitPerms = async () =>
|
||||
{
|
||||
await post(`/api/applications/${app.id}/permissions`, perms);
|
||||
};
|
||||
const [perms, updatePerms] = useState<Perms>(app.permissions);
|
||||
@ -77,29 +83,34 @@ const Application = ({ app }: { app: App }) => {
|
||||
</div>;
|
||||
};
|
||||
|
||||
const RoleComp = ({ role, onClick }: { role: Role, onClick: React.ReactEventHandler }) => {
|
||||
const RoleComp = ({ role, onClick }: { role: Role, onClick: React.ReactEventHandler }) =>
|
||||
{
|
||||
return <div className='card role' >
|
||||
{role.name} <span className="clickable" onClick={onClick}>X</span>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const Roles = ({ userRoles, roles }: { userRoles: Role[], roles: Role[] }) => {
|
||||
const Roles = ({ userRoles, roles }: { userRoles: Role[], roles: Role[] }) =>
|
||||
{
|
||||
|
||||
const [unequippedRoles, updateUnequipped] = useState<Role[]>(roles.filter(role => !userRoles.some(r => role.id === r.id)));
|
||||
const [equippedRoles, updateEquipped] = useState<Role[]>(userRoles);
|
||||
|
||||
const roleSelected = (role: Role) => {
|
||||
const roleSelected = (role: Role) =>
|
||||
{
|
||||
updateEquipped([...equippedRoles, role]);
|
||||
updateUnequipped(unequippedRoles.filter(r => r.id !== role.id));
|
||||
};
|
||||
const roleDeselected = (role: Role) => {
|
||||
const roleDeselected = (role: Role) =>
|
||||
{
|
||||
updateEquipped(equippedRoles.filter(r => r.id !== role.id));
|
||||
updateUnequipped([...unequippedRoles, role]);
|
||||
};
|
||||
return <Dropdown>
|
||||
|
||||
<Dropdown.Header className='role-selector'>
|
||||
{equippedRoles.map(role => <RoleComp key={role.id} role={role} onClick={(event) => {
|
||||
{equippedRoles.map(role => <RoleComp key={role.id} role={role} onClick={(event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
roleDeselected(role);
|
||||
}} />)}
|
||||
@ -109,7 +120,8 @@ const Roles = ({ userRoles, roles }: { userRoles: Role[], roles: Role[] }) => {
|
||||
|
||||
{unequippedRoles.map(role => <Dropdown.Item
|
||||
key={role.id}
|
||||
onClick={() => {
|
||||
onClick={() =>
|
||||
{
|
||||
roleSelected(role);
|
||||
}}
|
||||
>
|
||||
@ -121,16 +133,20 @@ const Roles = ({ userRoles, roles }: { userRoles: Role[], roles: Role[] }) => {
|
||||
</Dropdown>;
|
||||
};
|
||||
|
||||
const User = ({ user, roles }: { user: APIUser, roles: Role[] }) => {
|
||||
const User = ({ user, roles }: { user: APIUser, roles: Role[] }) =>
|
||||
{
|
||||
|
||||
// const [apps, updateApps] = useState<App[]>([]);
|
||||
const [perms, updatePerms] = useState<Perms>(user.permissions);
|
||||
const userContext = useContext(SelectedUserContext);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const appsResponse = await get(`/api/users/${user.id}/applications`);
|
||||
if (appsResponse.success) {
|
||||
if (appsResponse.success)
|
||||
{
|
||||
const a = appsResponse.data as App[];
|
||||
// updateApps(a);
|
||||
userContext.updateUser({ apps: a });
|
||||
@ -138,7 +154,8 @@ const User = ({ user, roles }: { user: APIUser, roles: Role[] }) => {
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const commitPerms = async () => {
|
||||
const commitPerms = async () =>
|
||||
{
|
||||
await post(`/api/users/${user.id}/permissions`, perms);
|
||||
};
|
||||
|
||||
@ -190,18 +207,22 @@ const User = ({ user, roles }: { user: APIUser, roles: Role[] }) => {
|
||||
</div>;
|
||||
};
|
||||
|
||||
const CreateUserField = ({ className }: { className: string }) => {
|
||||
const CreateUserField = ({ className }: { className: string }) =>
|
||||
{
|
||||
|
||||
const [link, setLink] = useState<string | null>(null);
|
||||
const getSignupCode = async () => {
|
||||
const getSignupCode = async () =>
|
||||
{
|
||||
const response = await get('/api/register/code');
|
||||
if (response.status === 200 && response.data) {
|
||||
if (response.status === 200 && response.data)
|
||||
{
|
||||
const link = `${window.location.origin}/register?code=${response.data.code}`;
|
||||
setLink(link);
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard: React.MouseEventHandler = (event) => {
|
||||
const copyToClipboard: React.MouseEventHandler = (event) =>
|
||||
{
|
||||
const element = event.target as HTMLElement;
|
||||
const { parentElement } = element;
|
||||
navigator.clipboard.writeText(element.innerText);
|
||||
@ -209,7 +230,8 @@ const CreateUserField = ({ className }: { className: string }) => {
|
||||
(parentElement.childNodes[parentElement.childNodes.length - 1] as HTMLElement).style.visibility = "visible";
|
||||
};
|
||||
|
||||
const closeToolTip: React.MouseEventHandler = (event) => {
|
||||
const closeToolTip: React.MouseEventHandler = (event) =>
|
||||
{
|
||||
(event.target as HTMLElement).style.visibility = "hidden";
|
||||
};
|
||||
|
||||
@ -230,7 +252,8 @@ const CreateUserField = ({ className }: { className: string }) => {
|
||||
|
||||
};
|
||||
|
||||
const Users = () => {
|
||||
const Users = () =>
|
||||
{
|
||||
|
||||
const [users, setUsers] = useState<APIUser[]>([]);
|
||||
const [roles, updateRoles] = useState<Role[]>([]);
|
||||
@ -239,14 +262,19 @@ const Users = () => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const result = await get('/api/users', { page });
|
||||
if (result.success && result.data) {
|
||||
if (result.success && result.data)
|
||||
{
|
||||
setError(null);
|
||||
setUsers(result.data.users as APIUser[]);
|
||||
setPages(result.data.pages);
|
||||
} else setError(result.message || 'Unknown error');
|
||||
}
|
||||
else
|
||||
setError(result.message || 'Unknown error');
|
||||
setLoading(false);
|
||||
const rolesResponse = await get('/api/roles', { all: true });
|
||||
if (rolesResponse.success && rolesResponse.data)
|
||||
@ -254,21 +282,25 @@ const Users = () => {
|
||||
})();
|
||||
}, [page]);
|
||||
|
||||
const UserWrapper = () => {
|
||||
const UserWrapper = () =>
|
||||
{
|
||||
const { id } = useParams();
|
||||
const user = users.find(u => u.id === id);
|
||||
if (!user) return <p>Unknown user</p>;
|
||||
if (!user)
|
||||
return <p>Unknown user</p>;
|
||||
return <User roles={roles} user={user} />;
|
||||
};
|
||||
|
||||
const navigate = useNavigate();
|
||||
const UserListWrapper = () => {
|
||||
const UserListWrapper = () =>
|
||||
{
|
||||
return <div className="user-list-wrapper row">
|
||||
<div className={`col-6-lg col-12 ld ${loading && 'loading'}`}>
|
||||
<h4>All Users</h4>
|
||||
<Table headerItems={['Username', 'ID']}>
|
||||
{users.map(user => <TableListEntry
|
||||
onClick={() => {
|
||||
onClick={() =>
|
||||
{
|
||||
navigate(user.id);
|
||||
}}
|
||||
key={user.id}
|
||||
@ -284,22 +316,28 @@ const Users = () => {
|
||||
</div>;
|
||||
};
|
||||
|
||||
const ApplicationWrapper = () => {
|
||||
const ApplicationWrapper = () =>
|
||||
{
|
||||
const { appid } = useParams();
|
||||
if (!appid) return null;
|
||||
if (!appid)
|
||||
return null;
|
||||
|
||||
const userContext = useContext(SelectedUserContext);
|
||||
const [app, setApp] = useState<App | null>(userContext.user?.apps.find(app => app.id === appid) || null);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (userContext.user) return;
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
if (userContext.user)
|
||||
return;
|
||||
const result = await get(`/api/applications/${appid}`);
|
||||
if (result.success)
|
||||
setApp(result.data as App);
|
||||
})();
|
||||
}, [appid]);
|
||||
|
||||
if (!app) return <p>Loading...</p>;
|
||||
if (!app)
|
||||
return <p>Loading...</p>;
|
||||
return <Application app={app} />;
|
||||
};
|
||||
|
||||
|
@ -6,11 +6,13 @@ import { post, get, del } from "../../util/Util";
|
||||
import { Application as App } from "../../@types/ApiStructures";
|
||||
import { Res } from "../../@types/Other";
|
||||
|
||||
const Application = ({ app }: {app: App}) => {
|
||||
const Application = ({ app }: {app: App}) =>
|
||||
{
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const deleteApp = async () => {
|
||||
const deleteApp = async () =>
|
||||
{
|
||||
// const response =
|
||||
await del(`/api/user/applications/${app.id}`);
|
||||
};
|
||||
@ -25,16 +27,20 @@ const Application = ({ app }: {app: App}) => {
|
||||
|
||||
};
|
||||
|
||||
const Applications = () => {
|
||||
const Applications = () =>
|
||||
{
|
||||
|
||||
const [applications, setApplications] = useState<App[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const response = await get('/api/user/applications') as Res;
|
||||
if (response.status === 200) setApplications(response.data as App[]);
|
||||
if (response.status === 200)
|
||||
setApplications(response.data as App[]);
|
||||
setLoading(false);
|
||||
})();
|
||||
}, []);
|
||||
@ -43,27 +49,33 @@ const Applications = () => {
|
||||
const nameField = useRef<HTMLInputElement>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const createApp: React.MouseEventHandler = async (event) => {
|
||||
const createApp: React.MouseEventHandler = async (event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
const button = event.target as HTMLButtonElement;
|
||||
|
||||
if (!nameField.current || !descField.current) return;
|
||||
if (!nameField.current || !descField.current)
|
||||
return;
|
||||
|
||||
const name = nameField.current?.value;
|
||||
if (!name) return setError('Missing name');
|
||||
if (!name)
|
||||
return setError('Missing name');
|
||||
|
||||
const description = descField.current?.value || null;
|
||||
nameField.current.value = '';
|
||||
descField.current.value = '';
|
||||
button.disabled = true;
|
||||
const response = await post('/api/user/applications', { name, description });
|
||||
if (response.status !== 200) setError(response.message || 'Unknown error');
|
||||
else setApplications((apps) => [...apps, response.data as App]);
|
||||
if (response.status !== 200)
|
||||
setError(response.message || 'Unknown error');
|
||||
else
|
||||
setApplications((apps) => [...apps, response.data as App]);
|
||||
|
||||
button.disabled = false;
|
||||
};
|
||||
|
||||
const Main = () => {
|
||||
const Main = () =>
|
||||
{
|
||||
return <div className="row">
|
||||
|
||||
<div className={`col ld ${loading && 'loading'}`}>
|
||||
@ -90,10 +102,12 @@ const Applications = () => {
|
||||
</div>;
|
||||
};
|
||||
|
||||
const ApplicationWrapper = () => {
|
||||
const ApplicationWrapper = () =>
|
||||
{
|
||||
const { id } = useParams();
|
||||
const application = applications.find(app => app.id === id);
|
||||
if(!application) return <p>Unknown application</p>;
|
||||
if(!application)
|
||||
return <p>Unknown application</p>;
|
||||
return <Application app={application} />;
|
||||
};
|
||||
|
||||
|
@ -4,33 +4,42 @@ import { useLoginContext } from "../../structures/UserContext";
|
||||
import { FileSelector } from "../../components/InputElements";
|
||||
import { ExternalProfile as EP, User } from "../../@types/ApiStructures";
|
||||
|
||||
const TwoFactorControls = ({ user }: {user: User}) => {
|
||||
const TwoFactorControls = ({ user }: {user: User}) =>
|
||||
{
|
||||
|
||||
const [twoFactorError, set2FAError] = useState<string | null>(null);
|
||||
const [displayInput, setDisplayInput] = useState(false);
|
||||
|
||||
const [qr, setQr] = useState<string | null>(null);
|
||||
const displayQr = async () => {
|
||||
const displayQr = async () =>
|
||||
{
|
||||
const response = await get('/api/user/2fa');
|
||||
if (response.status !== 200) return set2FAError(response.message || 'Uknown error');
|
||||
if (response.status !== 200)
|
||||
return set2FAError(response.message || 'Uknown error');
|
||||
setQr(response.message as string);
|
||||
};
|
||||
|
||||
const codeRef = useRef<HTMLInputElement>(null);
|
||||
const disable2FA: React.MouseEventHandler = async (event) => {
|
||||
const disable2FA: React.MouseEventHandler = async (event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
const code = codeRef.current?.value;
|
||||
if (!code) return;
|
||||
if (!code)
|
||||
return;
|
||||
const response = await post('/api/user/2fa/disable', { code });
|
||||
if (response.status !== 200) return set2FAError(response.message || 'Unknown error');
|
||||
if (response.status !== 200)
|
||||
return set2FAError(response.message || 'Unknown error');
|
||||
};
|
||||
|
||||
const submitCode: React.MouseEventHandler = async (event) => {
|
||||
const submitCode: React.MouseEventHandler = async (event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
const code = codeRef.current?.value;
|
||||
if (!code) return;
|
||||
if (!code)
|
||||
return;
|
||||
const response = await post('/api/user/2fa/verify', { code });
|
||||
if (response.status !== 200) return set2FAError(response.message || 'Unknown error');
|
||||
if (response.status !== 200)
|
||||
return set2FAError(response.message || 'Unknown error');
|
||||
};
|
||||
|
||||
let inner = <div>
|
||||
@ -46,7 +55,8 @@ const TwoFactorControls = ({ user }: {user: User}) => {
|
||||
</div>;
|
||||
|
||||
|
||||
if (user.twoFactor) inner = <div>
|
||||
if (user.twoFactor)
|
||||
inner = <div>
|
||||
{displayInput ?
|
||||
<form>
|
||||
<input placeholder='Authenticator code to disable' ref={codeRef} type='password' />
|
||||
@ -62,7 +72,8 @@ const TwoFactorControls = ({ user }: {user: User}) => {
|
||||
|
||||
};
|
||||
|
||||
const ExternalProfile = ({ profile }: {profile: EP}) => {
|
||||
const ExternalProfile = ({ profile }: {profile: EP}) =>
|
||||
{
|
||||
return <div>
|
||||
<b>{capitalise(profile.provider)}</b>
|
||||
<p className="m-0">Username: {profile.username}</p>
|
||||
@ -70,7 +81,8 @@ const ExternalProfile = ({ profile }: {profile: EP}) => {
|
||||
</div>;
|
||||
};
|
||||
|
||||
const ThirdPartyConnections = ({ user }: {user: User}) => {
|
||||
const ThirdPartyConnections = ({ user }: {user: User}) =>
|
||||
{
|
||||
|
||||
const { externalProfiles } = user;
|
||||
|
||||
@ -80,7 +92,8 @@ const ThirdPartyConnections = ({ user }: {user: User}) => {
|
||||
|
||||
};
|
||||
|
||||
const Profile = () => {
|
||||
const Profile = () =>
|
||||
{
|
||||
|
||||
const user = useLoginContext()[0] as User;
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
@ -92,8 +105,10 @@ const Profile = () => {
|
||||
const newPassRef = useRef<HTMLInputElement>(null);
|
||||
const newPassRepeatRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const submit: React.MouseEventHandler = async (event) => {
|
||||
if (!file) return;
|
||||
const submit: React.MouseEventHandler = async (event) =>
|
||||
{
|
||||
if (!file)
|
||||
return;
|
||||
const button = event.target as HTMLButtonElement;
|
||||
button.disabled = true;
|
||||
|
||||
@ -103,12 +118,15 @@ const Profile = () => {
|
||||
const response = await post('/api/user/avatar', body, {
|
||||
headers: null
|
||||
});
|
||||
if (!response.success) setError(response.message || 'Unknown error');
|
||||
else setFile(null);
|
||||
if (!response.success)
|
||||
setError(response.message || 'Unknown error');
|
||||
else
|
||||
setFile(null);
|
||||
button.disabled = false;
|
||||
};
|
||||
|
||||
const updateSettings: React.MouseEventHandler = async (event) => {
|
||||
const updateSettings: React.MouseEventHandler = async (event) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
const button = event.target as HTMLButtonElement;
|
||||
|
||||
@ -118,12 +136,15 @@ const Profile = () => {
|
||||
const newPassword = newPassRef.current?.value;
|
||||
const newPasswordRepeat = newPassRepeatRef.current?.value;
|
||||
|
||||
if (!currentPassword) return setError('Missing password');
|
||||
if (!currentPassword)
|
||||
return setError('Missing password');
|
||||
|
||||
button.disabled = true;
|
||||
const data: {username?: string, displayName?: string, password?: string, newPassword?: string} = { username, displayName, password: currentPassword };
|
||||
if (currentPassword && newPassword && newPasswordRepeat) {
|
||||
if (newPassword !== newPasswordRepeat) {
|
||||
if (currentPassword && newPassword && newPasswordRepeat)
|
||||
{
|
||||
if (newPassword !== newPasswordRepeat)
|
||||
{
|
||||
button.disabled = false;
|
||||
return setError('Passwords do not match');
|
||||
}
|
||||
|
@ -12,7 +12,8 @@ type NavLinkOptions = {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const NavLink = React.forwardRef(({ activeClassName, activeStyle, ...props }: NavLinkOptions, ref: React.ForwardedRef<HTMLAnchorElement>) => {
|
||||
const NavLink = React.forwardRef(({ activeClassName, activeStyle, ...props }: NavLinkOptions, ref: React.ForwardedRef<HTMLAnchorElement>) =>
|
||||
{
|
||||
return <BaseNavLink
|
||||
ref={ref}
|
||||
{...props}
|
||||
|
@ -2,9 +2,11 @@ import React from 'react';
|
||||
import { Navigate, useLocation } from "react-router-dom";
|
||||
import { getUser } from "../util/Util";
|
||||
|
||||
export const PrivateRoute = ({ children }: {children: JSX.Element}) => {
|
||||
export const PrivateRoute = ({ children }: {children: JSX.Element}) =>
|
||||
{
|
||||
const user = getUser();
|
||||
const location = useLocation();
|
||||
if (!user) return <Navigate to='/login' replace state={{ from: location }} />;
|
||||
if (!user)
|
||||
return <Navigate to='/login' replace state={{ from: location }} />;
|
||||
return children;
|
||||
};
|
@ -2,8 +2,10 @@ import React from 'react';
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { getUser } from "../util/Util";
|
||||
|
||||
export const UnauthedRoute = ({ children }: {children: JSX.Element}) => {
|
||||
export const UnauthedRoute = ({ children }: {children: JSX.Element}) =>
|
||||
{
|
||||
const user = getUser();
|
||||
if (user) return <Navigate to='/home' replace />;
|
||||
if (user)
|
||||
return <Navigate to='/home' replace />;
|
||||
return children;
|
||||
};
|
@ -5,18 +5,22 @@ import {User} from '../@types/ApiStructures';
|
||||
type UpdateFunc = ((user?: User | null) => void)
|
||||
|
||||
const LoginContext = React.createContext<User | null>(null);
|
||||
const LoginUpdateContext = React.createContext<UpdateFunc>(() => { /* */ });
|
||||
const LoginUpdateContext = React.createContext<UpdateFunc>(() =>
|
||||
{ /* */ });
|
||||
|
||||
// Hook
|
||||
export const useLoginContext = (): [User | null, UpdateFunc] => {
|
||||
export const useLoginContext = (): [User | null, UpdateFunc] =>
|
||||
{
|
||||
return [useContext(LoginContext), useContext(LoginUpdateContext)];
|
||||
};
|
||||
|
||||
// Component
|
||||
export const UserContext = ({ children }: {children: React.ReactNode}) => {
|
||||
export const UserContext = ({ children }: {children: React.ReactNode}) =>
|
||||
{
|
||||
|
||||
const [user, setLoginState] = useState(getUser());
|
||||
const updateLoginState = () => {
|
||||
const updateLoginState = () =>
|
||||
{
|
||||
setLoginState(getUser());
|
||||
};
|
||||
|
||||
|
@ -1,18 +1,23 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
|
||||
// Listens for mouse clicks outside of the wrapped element
|
||||
const alerter = (ref: React.RefObject<HTMLElement>, callback: () => void) => {
|
||||
useEffect(() => {
|
||||
const alerter = (ref: React.RefObject<HTMLElement>, callback: () => void) =>
|
||||
{
|
||||
useEffect(() =>
|
||||
{
|
||||
|
||||
const listener = (event: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
const listener = (event: MouseEvent) =>
|
||||
{
|
||||
if (ref.current && !ref.current.contains(event.target as Node))
|
||||
{
|
||||
return callback();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', listener);
|
||||
|
||||
return () => {
|
||||
return () =>
|
||||
{
|
||||
document.removeEventListener('mousedown', listener);
|
||||
};
|
||||
|
||||
@ -25,7 +30,8 @@ const alerter = (ref: React.RefObject<HTMLElement>, callback: () => void) => {
|
||||
* @param {{children: React.ReactNode, callback: () => void}} { children, callback }
|
||||
* @return {*}
|
||||
*/
|
||||
const ClickDetector = ({ children, callback }: {children: React.ReactNode, callback: () => void}) => {
|
||||
const ClickDetector = ({ children, callback }: {children: React.ReactNode, callback: () => void}) =>
|
||||
{
|
||||
|
||||
const wrapperRef = useRef(null);
|
||||
alerter(wrapperRef, callback);
|
||||
|
@ -9,23 +9,28 @@ type StateProps = {
|
||||
error: boolean
|
||||
}
|
||||
|
||||
class ErrorBoundary extends React.Component<BoundaryProps, StateProps> {
|
||||
class ErrorBoundary extends React.Component<BoundaryProps, StateProps>
|
||||
{
|
||||
|
||||
fallback?: React.ReactNode;
|
||||
|
||||
constructor(props: BoundaryProps) {
|
||||
constructor(props: BoundaryProps)
|
||||
{
|
||||
super(props);
|
||||
this.state = { error: false };
|
||||
this.fallback = props.fallback;
|
||||
}
|
||||
|
||||
override componentDidCatch(error: Error, errorInfo: unknown) {
|
||||
override componentDidCatch(error: Error, errorInfo: unknown)
|
||||
{
|
||||
this.setState({error: true});
|
||||
console.error(error, errorInfo);
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.state.error) return this.fallback || <h1>Something went wrong :/</h1>;
|
||||
override render()
|
||||
{
|
||||
if (this.state.error)
|
||||
return this.fallback || <h1>Something went wrong :/</h1>;
|
||||
return this.props.children;
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,14 @@
|
||||
import React, { useRef } from "react";
|
||||
import { Permissions as Perms } from "../@types/ApiStructures";
|
||||
|
||||
export const Permission = ({ name, value, chain, updatePerms }: {name: string, value: number | Perms, chain: string, updatePerms: (chain: string, perm: string) => void}) => {
|
||||
export const Permission = ({ name, value, chain, updatePerms }: {name: string, value: number | Perms, chain: string, updatePerms: (chain: string, perm: string) => void}) =>
|
||||
{
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const onChange = () => {
|
||||
const onChange = () =>
|
||||
{
|
||||
const val = inputRef.current?.value || null;
|
||||
if (val === null) return;
|
||||
if (val === null)
|
||||
return;
|
||||
updatePerms(chain, val);
|
||||
};
|
||||
|
||||
@ -15,13 +18,17 @@ export const Permission = ({ name, value, chain, updatePerms }: {name: string, v
|
||||
</li>;
|
||||
};
|
||||
|
||||
export const PermissionGroup = ({ name, value, chain, updatePerms }: {name: string, value: number | Perms, chain: string, updatePerms: (chain: string, perm: string) => void}) => {
|
||||
export const PermissionGroup = ({ name, value, chain, updatePerms }: {name: string, value: number | Perms, chain: string, updatePerms: (chain: string, perm: string) => void}) =>
|
||||
{
|
||||
const elements = [];
|
||||
|
||||
for (const [perm, val] of Object.entries(value)) {
|
||||
for (const [perm, val] of Object.entries(value))
|
||||
{
|
||||
const props = { key: perm, name: perm, value: val, updatePerms, chain: `${chain}:${perm}` };
|
||||
if(typeof val ==='object') elements.push(<PermissionGroup {...props} />);
|
||||
else elements.push(<Permission {...props} />);
|
||||
if(typeof val ==='object')
|
||||
elements.push(<PermissionGroup {...props} />);
|
||||
else
|
||||
elements.push(<Permission {...props} />);
|
||||
}
|
||||
return <li>
|
||||
<div className="groupName">
|
||||
@ -33,27 +40,35 @@ export const PermissionGroup = ({ name, value, chain, updatePerms }: {name: stri
|
||||
</li>;
|
||||
};
|
||||
|
||||
export const Permissions = ({ perms, onUpdate }: { perms: Perms, onUpdate: (perms: Perms) => void }) => {
|
||||
export const Permissions = ({ perms, onUpdate }: { perms: Perms, onUpdate: (perms: Perms) => void }) =>
|
||||
{
|
||||
|
||||
const updatePerms = (chain: string, raw: string) => {
|
||||
const updatePerms = (chain: string, raw: string) =>
|
||||
{
|
||||
const value = parseInt(raw);
|
||||
if (isNaN(value)) return;
|
||||
if (isNaN(value))
|
||||
return;
|
||||
|
||||
let selected = perms;
|
||||
const keys = chain.split(':');
|
||||
for (const key of keys) {
|
||||
if (key === keys[keys.length - 1]) selected[key] = value;
|
||||
else selected = selected[key] as Perms;
|
||||
for (const key of keys)
|
||||
{
|
||||
if (key === keys[keys.length - 1])
|
||||
selected[key] = value;
|
||||
else
|
||||
selected = selected[key] as Perms;
|
||||
}
|
||||
onUpdate(perms);
|
||||
};
|
||||
|
||||
const elements = [];
|
||||
const keys = Object.keys(perms);
|
||||
for (const perm of keys) {
|
||||
for (const perm of keys)
|
||||
{
|
||||
const props = { key: perm, name: perm, value: perms[perm], chain: perm, updatePerms };
|
||||
let Elem = null;
|
||||
switch (typeof perms[perm]) {
|
||||
switch (typeof perms[perm])
|
||||
{
|
||||
case 'number':
|
||||
Elem = Permission;
|
||||
break;
|
||||
@ -62,7 +77,8 @@ export const Permissions = ({ perms, onUpdate }: { perms: Perms, onUpdate: (perm
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line react/display-name
|
||||
Elem = () => {
|
||||
Elem = () =>
|
||||
{
|
||||
return <p>Uknown permission structure</p>;
|
||||
};
|
||||
break;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, {Fragment} from "react";
|
||||
import { RateLimit as RL, RateLimits as RLs } from "../@types/ApiStructures";
|
||||
|
||||
const RateLimit = ({route, limit}: {route: string, limit: RL}) => {
|
||||
const RateLimit = ({route, limit}: {route: string, limit: RL}) =>
|
||||
{
|
||||
|
||||
return <Fragment>
|
||||
<p><b>{route}</b></p>
|
||||
@ -16,10 +17,12 @@ const RateLimit = ({route, limit}: {route: string, limit: RL}) => {
|
||||
</Fragment>;
|
||||
};
|
||||
|
||||
export const RateLimits = ({ rateLimits }: {rateLimits: RLs}) => {
|
||||
export const RateLimits = ({ rateLimits }: {rateLimits: RLs}) =>
|
||||
{
|
||||
|
||||
const routes = Object.keys(rateLimits);
|
||||
const elements = routes.map((route, index) => {
|
||||
const elements = routes.map((route, index) =>
|
||||
{
|
||||
return <div key={index} className="card">
|
||||
<RateLimit {...{route, limit:rateLimits[route], index}} />
|
||||
</div>;
|
||||
|
Loading…
Reference in New Issue
Block a user