bunch of bullshit
This commit is contained in:
parent
b5090c02ea
commit
40d5d9da4a
104
src/App.js
104
src/App.js
@ -39,7 +39,12 @@ function App() {
|
||||
}, []);
|
||||
|
||||
const menuItems = [
|
||||
{ to: '/home', label: 'Home', items: [{ to: '/profile', label: 'Profile', relative: true }] },
|
||||
{
|
||||
to: '/home', label: 'Home', items: [
|
||||
{ to: '/profile', label: 'Profile', relative: true },
|
||||
{ to: '/applications', label: 'Applications', relative: true }
|
||||
]
|
||||
},
|
||||
{ to: '/users', label: 'Users' },
|
||||
{ to: '/admin', label: 'Admin' }
|
||||
];
|
||||
@ -51,55 +56,58 @@ function App() {
|
||||
|
||||
<div className='background'>
|
||||
|
||||
{user ?
|
||||
<div>
|
||||
<header className="card">
|
||||
<UserControls />
|
||||
</header>
|
||||
<Sidebar>
|
||||
<SidebarMenu menuItems={menuItems} />
|
||||
</Sidebar>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<div className={`main-content ${user ? "" : "login"}`}>
|
||||
<ErrorBoundary>
|
||||
<Routes>
|
||||
|
||||
<Route path='/home/*' element={<PrivateRoute>
|
||||
<TitledPage title='Home'>
|
||||
<Home />
|
||||
</TitledPage>
|
||||
</PrivateRoute >} />
|
||||
|
||||
<Route path='/users/*' element={<PrivateRoute>
|
||||
<TitledPage title='Users' >
|
||||
<Users />
|
||||
</TitledPage>
|
||||
</PrivateRoute >} />
|
||||
|
||||
<Route path='/admin/*' element={<PrivateRoute>
|
||||
<Admin />
|
||||
</PrivateRoute >} />
|
||||
|
||||
<Route path='/login/*' element={<UnauthedRoute>
|
||||
<Login />
|
||||
</UnauthedRoute>} />
|
||||
|
||||
<Route path='/register/*' element={<UnauthedRoute>
|
||||
<Register />
|
||||
</UnauthedRoute>} />
|
||||
|
||||
<Route path='*' element={<Navigate to='/home' />} />
|
||||
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
|
||||
<div className={`footer ${user ? "" : "login"}`}>
|
||||
<p className='m-0'>Made with ❤️ by <a href="https://corgi.wtf" target="_blank" rel="noreferrer">Navy.gif</a> | CSS by <a href="https://d3vision.dev" target="_blank" rel="noreferrer">D3vision</a></p>
|
||||
</div>
|
||||
{user ?
|
||||
<div>
|
||||
<header className="card">
|
||||
<UserControls />
|
||||
</header>
|
||||
<Sidebar>
|
||||
<SidebarMenu menuItems={menuItems} />
|
||||
</Sidebar>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<div className={`main-content ${user ? "" : "login"}`}>
|
||||
|
||||
<ErrorBoundary>
|
||||
|
||||
<Routes>
|
||||
|
||||
<Route path='/home/*' element={<PrivateRoute>
|
||||
<TitledPage title='Home'>
|
||||
<Home />
|
||||
</TitledPage>
|
||||
</PrivateRoute >} />
|
||||
|
||||
<Route path='/users/*' element={<PrivateRoute>
|
||||
<TitledPage title='Users' >
|
||||
<Users />
|
||||
</TitledPage>
|
||||
</PrivateRoute >} />
|
||||
|
||||
<Route path='/admin/*' element={<PrivateRoute>
|
||||
<TitledPage title='Admin'>
|
||||
<Admin />
|
||||
</TitledPage>
|
||||
</PrivateRoute >} />
|
||||
|
||||
<Route path='/login/*' element={<UnauthedRoute>
|
||||
<Login />
|
||||
</UnauthedRoute>} />
|
||||
|
||||
<Route path='/register/*' element={<UnauthedRoute>
|
||||
<Register />
|
||||
</UnauthedRoute>} />
|
||||
|
||||
<Route path='*' element={<Navigate to='/home' />} />
|
||||
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
|
||||
<div className={`footer ${user ? "" : "login"}`}>
|
||||
<p className='m-0'>Made with ❤️ by <a href="https://corgi.wtf" target="_blank" rel="noreferrer">Navy.gif</a> | CSS by <a href="https://d3vision.dev" target="_blank" rel="noreferrer">D3vision</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
36
src/components/FileSelector.js
Normal file
36
src/components/FileSelector.js
Normal file
@ -0,0 +1,36 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
const FileSelector = ({cb}) => {
|
||||
|
||||
if (!cb) throw new Error('Missing callback');
|
||||
const [file, setFile] = useState(null);
|
||||
|
||||
const onDragOver = (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const onDrop = (event) => {
|
||||
event.preventDefault();
|
||||
const { dataTransfer } = event;
|
||||
if (!dataTransfer.files.length) return;
|
||||
|
||||
const [file] = dataTransfer.files;
|
||||
setFile(file);
|
||||
cb(file);
|
||||
};
|
||||
|
||||
return <label onDrop={onDrop} onDragOver={onDragOver} htmlFor="pfp_upload" className="drop-container">
|
||||
<span className="drop-title">Drag 'n' drop a file here</span>
|
||||
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) => {
|
||||
setFile(event.target.files[0]);
|
||||
cb(event.target.files[0]);
|
||||
}}
|
||||
type="file" id="pfp_upload" accept="image/*" required></input>
|
||||
</label>;
|
||||
};
|
||||
|
||||
export default FileSelector;
|
@ -9,6 +9,7 @@ export const TableListEntry = ({onClick, item, itemKeys}) => {
|
||||
|
||||
export const Table = ({headerItems, children, items, itemKeys}) => {
|
||||
|
||||
if (!headerItems) throw new Error(`Missing table headers`);
|
||||
let i = 0;
|
||||
return <table className="striped hover" >
|
||||
<thead>
|
||||
|
@ -18,6 +18,10 @@
|
||||
--font-family-mono: monaco, "Consolas", "Lucida Console", monospace;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.w-100{
|
||||
width: 100% !important;
|
||||
}
|
||||
|
@ -1,12 +1,33 @@
|
||||
import React from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import FileSelector from "../components/FileSelector";
|
||||
import '../css/pages/Admin.css';
|
||||
|
||||
const Admin = () => {
|
||||
|
||||
return <div className="page">
|
||||
<h2 className="pageTitle">Admin</h2>
|
||||
<div className='card'>
|
||||
const [file, setFile] = useState(null);
|
||||
|
||||
const submit = (event) => {
|
||||
event.preventDefault();
|
||||
console.log(file);
|
||||
};
|
||||
|
||||
return <div className="row">
|
||||
|
||||
<div className="col-6-lg col-12">
|
||||
<h2>Thematic customisation</h2>
|
||||
<form>
|
||||
<input type='text' placeholder='Site name' />
|
||||
<FileSelector cb={setFile} />
|
||||
<button onClick={submit}>Submit</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="col-6-lg col-12">
|
||||
<h2>Dingus</h2>
|
||||
|
||||
</div>
|
||||
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
@ -1,190 +1,14 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import React from "react";
|
||||
import { Route, Routes } from "react-router";
|
||||
import '../css/pages/Home.css';
|
||||
import { useLoginContext } from "../structures/UserContext";
|
||||
import ErrorBoundary from "../util/ErrorBoundary";
|
||||
import { capitalise, get, post } from "../util/Util";
|
||||
|
||||
const TwoFactorControls = ({ user }) => {
|
||||
|
||||
const [twoFactorError, set2FAError] = useState(null);
|
||||
const [displayInput, setDisplayInput] = useState(false);
|
||||
|
||||
const [qr, setQr] = useState(null);
|
||||
const displayQr = async () => {
|
||||
const response = await get('/api/user/2fa');
|
||||
if (response.status !== 200) return set2FAError(response.message);
|
||||
setQr(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 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();
|
||||
const [file, setFile] = useState(null);
|
||||
const fileSelector = useRef();
|
||||
const [fileName, setFileName] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// For some reason this has to be done for onDrop to work
|
||||
const onDragOver = (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const onDrop = (event) => {
|
||||
event.preventDefault();
|
||||
const { dataTransfer } = event;
|
||||
if (!dataTransfer.files.length) return;
|
||||
|
||||
const [file] = dataTransfer.files;
|
||||
console.log(file.name);
|
||||
setFileName(`Selected: ${file.name}`);
|
||||
setFile(file);
|
||||
};
|
||||
|
||||
const fileGarbage = (event) => {
|
||||
setFile(event.target.files[0]);
|
||||
setFileName(`Selected: ${event.target.files[0].name}`);
|
||||
};
|
||||
|
||||
const submit = async ({target: button}) => {
|
||||
button.disabled = true;
|
||||
if (!file) return;
|
||||
|
||||
const body = new FormData();
|
||||
body.set('file', file, file.name);
|
||||
// body.set('name', file.name);
|
||||
|
||||
const response = await post('/api/user/avatar', body, { headers: null });
|
||||
if (!response.success) setError(response.message);
|
||||
else fileSelector.current.value = null;
|
||||
button.disabled = false;
|
||||
};
|
||||
|
||||
return <div className="row">
|
||||
|
||||
<div className="col-6-lg col-12">
|
||||
<h3>Profile</h3>
|
||||
<div className="dir-row pfp-wrapper">
|
||||
<div className="w-auto f-s0">
|
||||
<p><b>Profile Picture</b></p>
|
||||
<img width={256} height={256} src={`/api/users/${user.id}/avatar`} />
|
||||
</div>
|
||||
<div className="f-g f-b0">
|
||||
<p><b>Change Profile Picture</b></p>
|
||||
<form className="f-g f-b0">
|
||||
<label onDrop={onDrop} onDragOver={onDragOver} htmlFor="pfp_upload" className="drop-container">
|
||||
<span className="drop-title">Drag 'n' drop a file here</span>
|
||||
or
|
||||
<span className="drop-title">Click to select a file</span>
|
||||
<p className="fileName m-0">{fileName}</p>
|
||||
<input ref={fileSelector} onChange={(event) => fileGarbage(event)}
|
||||
type="file" id="pfp_upload" accept="image/*" required></input>
|
||||
</label>
|
||||
<button onClick={submit} className="button primary mt-3">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<h4 className="mt-5">Third party connections</h4>
|
||||
<ThirdPartyConnections user={user} />
|
||||
|
||||
</div>
|
||||
|
||||
<div className="col-6-lg col-12">
|
||||
<h3>Settings</h3>
|
||||
|
||||
<p><b>ID:</b> {user.id}</p>
|
||||
|
||||
<form>
|
||||
<label>Username</label>
|
||||
<input id='username' defaultValue={user.username} type='text' autoComplete="off" />
|
||||
|
||||
<label>Display Name</label>
|
||||
<input id='displayName' defaultValue={user.displayName} type='text' autoComplete="off" />
|
||||
|
||||
<label>Change password</label>
|
||||
<input id='currentPassword' placeholder="Current password" type='password' autoComplete="off" />
|
||||
<input id='newPassword' placeholder="New password" type='password' autoComplete="off" />
|
||||
<input id='newPasswordRepeat' placeholder="Repeat new password" type='password' autoComplete="off" />
|
||||
|
||||
<button className="button primary">Save</button>
|
||||
</form>
|
||||
|
||||
<p className="mb-0 mt-5"><b>Two Factor:</b> {user.twoFactor ? 'enabled' : 'disabled'}</p>
|
||||
<TwoFactorControls user={user} />
|
||||
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
import Applications from "./home/Applications";
|
||||
import Profile from "./home/Profile";
|
||||
|
||||
const Main = () => {
|
||||
|
||||
return <div>
|
||||
|
||||
What to put here? hmmm
|
||||
</div>;
|
||||
};
|
||||
|
||||
@ -194,6 +18,7 @@ const Home = () => {
|
||||
<Routes>
|
||||
<Route path="/" element={<Main />} />
|
||||
<Route path="/profile" element={<Profile />} />
|
||||
<Route path="/applications/*" element={<Applications />} />
|
||||
</Routes>
|
||||
</ErrorBoundary>;
|
||||
};
|
||||
|
@ -68,7 +68,7 @@ const ApplicationList = ({ apps }) => {
|
||||
return <Table headerItems={['Name', 'ID']}>
|
||||
{apps.map(app => <TableListEntry
|
||||
onClick={() => {
|
||||
navigate(`${window.location.pathname}/applications/${app.id}`);
|
||||
navigate(`applications/${app.id}`);
|
||||
}}
|
||||
key={app.id}
|
||||
item={app}
|
||||
@ -219,7 +219,7 @@ const Users = () => {
|
||||
const UserWrapper = () => {
|
||||
const { id } = useParams();
|
||||
const user = users.find(u => u.id === id);
|
||||
if (!user) return null;
|
||||
if (!user) return <p>Unknown user</p>;
|
||||
return <User user={user} />;
|
||||
};
|
||||
|
||||
|
102
src/pages/home/Applications.js
Normal file
102
src/pages/home/Applications.js
Normal file
@ -0,0 +1,102 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Route, Routes, useNavigate, useParams } from "react-router";
|
||||
import { Table, TableListEntry } from "../../components/Table";
|
||||
import ErrorBoundary from "../../util/ErrorBoundary";
|
||||
import { post, get, del } from "../../util/Util";
|
||||
|
||||
const Application = ({ app }) => {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const deleteApp = async () => {
|
||||
const response = await del(`/api/user/applications/${app.id}`);
|
||||
};
|
||||
|
||||
return <div>
|
||||
<button className="button secondary" onClick={() => navigate(-1)}>Back</button>
|
||||
<h2>{app.name}</h2>
|
||||
<p>{app.description}</p>
|
||||
<p>{app.token}</p>
|
||||
<button onClick={deleteApp}>Delete</button>
|
||||
</div>;
|
||||
|
||||
};
|
||||
|
||||
const Applications = () => {
|
||||
|
||||
const [applications, setApplications] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const response = await get('/api/user/applications');
|
||||
if (response.status === 200) setApplications(response.data);
|
||||
setLoading(false);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const descField = useRef();
|
||||
const nameField = useRef();
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const createApp = async (event) => {
|
||||
event.preventDefault();
|
||||
const button = event.target;
|
||||
|
||||
const name = nameField.current.value;
|
||||
if(!name) return setError('Missing name');
|
||||
const description = descField.current.value;
|
||||
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);
|
||||
else setApplications((apps) => [...apps, response.data]);
|
||||
|
||||
button.disabled = false;
|
||||
};
|
||||
|
||||
const Main = () => {
|
||||
return <div className="row">
|
||||
|
||||
<div className={`col ld ${loading && 'loading'}`}>
|
||||
<h2>Applications</h2>
|
||||
<Table headerItems={['Name', 'ID']}>
|
||||
{applications.map(application => <TableListEntry
|
||||
key={application.id}
|
||||
onClick={() => navigate(application.id)}
|
||||
item={application}
|
||||
itemKeys={['name', 'id']}
|
||||
/>)}
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<div className="col">
|
||||
<h2>Create Application</h2>
|
||||
{error && <p>{error}</p>}
|
||||
<form>
|
||||
<input ref={nameField} placeholder="Name" type='text' />
|
||||
<textarea ref={descField} rows={5} placeholder="Describe your application" />
|
||||
<button onClick={createApp} className="button primary mt-3">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const ApplicationWrapper = () => {
|
||||
const { id } = useParams();
|
||||
const application = applications.find(app => app.id === id);
|
||||
if(!application) return <p>Unknown application</p>;
|
||||
return <Application app={application} />;
|
||||
};
|
||||
|
||||
return <ErrorBoundary>
|
||||
<Routes>
|
||||
<Route exact path="/" element={<Main />} />
|
||||
<Route path="/:id" element={<ApplicationWrapper />} />
|
||||
</Routes>
|
||||
</ErrorBoundary>;
|
||||
};
|
||||
|
||||
export default Applications;
|
187
src/pages/home/Profile.js
Normal file
187
src/pages/home/Profile.js
Normal file
@ -0,0 +1,187 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { capitalise, get, post } from "../../util/Util";
|
||||
import { useLoginContext } from "../../structures/UserContext";
|
||||
import FileSelector from "../../components/FileSelector";
|
||||
|
||||
const TwoFactorControls = ({ user }) => {
|
||||
|
||||
const [twoFactorError, set2FAError] = useState(null);
|
||||
const [displayInput, setDisplayInput] = useState(false);
|
||||
|
||||
const [qr, setQr] = useState(null);
|
||||
const displayQr = async () => {
|
||||
const response = await get('/api/user/2fa');
|
||||
if (response.status !== 200) return set2FAError(response.message);
|
||||
setQr(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 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();
|
||||
const [file, setFile] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const fileSelector = useRef();
|
||||
const usernameRef = useRef();
|
||||
const displayNameRef = useRef();
|
||||
const currPassRef = useRef();
|
||||
const newPassRef = useRef();
|
||||
const newPassRepeatRef = useRef();
|
||||
|
||||
const submit = async ({ target: button }) => {
|
||||
button.disabled = true;
|
||||
if (!file) return;
|
||||
|
||||
const body = new FormData();
|
||||
body.set('file', file, file.name);
|
||||
// body.set('name', file.name);
|
||||
|
||||
const response = await post('/api/user/avatar', body, { headers: null });
|
||||
if (!response.success) setError(response.message);
|
||||
else fileSelector.current.value = null;
|
||||
button.disabled = false;
|
||||
};
|
||||
|
||||
const updateSettings = async (event) => {
|
||||
event.preventDefault();
|
||||
const button = event.target;
|
||||
|
||||
const username = usernameRef.current.value;
|
||||
const displayName = displayNameRef.current.value;
|
||||
const currentPassword = currPassRef.current.value;
|
||||
const newPassword = newPassRef.current.value;
|
||||
const newPasswordRepeat = newPassRepeatRef.current.value;
|
||||
|
||||
if (!currentPassword) return setError('Missing password');
|
||||
|
||||
button.disabled = true;
|
||||
const data = { username, displayName, password: currentPassword };
|
||||
if (currentPassword && newPassword && newPasswordRepeat) {
|
||||
if (newPassword !== newPasswordRepeat) {
|
||||
button.disabled = false;
|
||||
return setError('Passwords do not match');
|
||||
}
|
||||
data.newPassword = newPassword;
|
||||
}
|
||||
const response = await post('/api/user/settings', data);
|
||||
console.log(response);
|
||||
button.disabled = false;
|
||||
|
||||
};
|
||||
|
||||
return <div className="row">
|
||||
|
||||
<div className="col-6-lg col-12">
|
||||
<h3>Profile</h3>
|
||||
<div className="dir-row pfp-wrapper">
|
||||
<div className="w-auto f-s0">
|
||||
<p><b>Profile Picture</b></p>
|
||||
<img draggable={false} width={256} height={256} src={`/api/users/${user.id}/avatar`} />
|
||||
</div>
|
||||
<div className="f-g f-b0">
|
||||
<p><b>Change Profile Picture</b></p>
|
||||
<form className="f-g f-b0">
|
||||
<FileSelector cb={setFile} />
|
||||
<button onClick={submit} className="button primary mt-3">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<h4 className="mt-5">Third party connections</h4>
|
||||
<ThirdPartyConnections user={user} />
|
||||
|
||||
</div>
|
||||
|
||||
<div className="col-6-lg col-12">
|
||||
<h3>Settings</h3>
|
||||
|
||||
<p><b>ID:</b> {user.id}</p>
|
||||
|
||||
<p>{error}</p>
|
||||
|
||||
<form>
|
||||
<label>Username</label>
|
||||
<input ref={usernameRef} id='username' defaultValue={user.username} type='text' autoComplete="off" />
|
||||
|
||||
<label>Display Name</label>
|
||||
<input ref={displayNameRef} id='displayName' defaultValue={user.displayName} type='text' autoComplete="off" />
|
||||
|
||||
<label>Change password</label>
|
||||
<input ref={currPassRef} id='currentPassword' placeholder="Current password" type='password' autoComplete="off" />
|
||||
<input ref={newPassRef} id='newPassword' placeholder="New password" type='password' autoComplete="off" />
|
||||
<input ref={newPassRepeatRef} id='newPasswordRepeat' placeholder="Repeat new password" type='password' autoComplete="off" />
|
||||
|
||||
<button onClick={updateSettings} className="button primary">Save</button>
|
||||
</form>
|
||||
|
||||
<p className="mb-0 mt-5"><b>Two Factor:</b> {user.twoFactor ? 'enabled' : 'disabled'}</p>
|
||||
<TwoFactorControls user={user} />
|
||||
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default Profile;
|
@ -46,6 +46,7 @@ const parseResponse = async (response) => {
|
||||
|
||||
if (status === 401) {
|
||||
clearSession();
|
||||
if(!location.pathname.includes('/login')) location.pathname = '/login';
|
||||
}
|
||||
|
||||
if (headers['content-type']?.includes('application/json')) {
|
||||
@ -83,6 +84,25 @@ export const get = async (url, params) => {
|
||||
return parseResponse(response);
|
||||
};
|
||||
|
||||
export const del = async (url, body, opts = {}) => {
|
||||
const options = {
|
||||
method: 'delete',
|
||||
body
|
||||
};
|
||||
|
||||
if (opts.headers !== null) {
|
||||
options.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...opts.headers || {}
|
||||
};
|
||||
}
|
||||
|
||||
if (options.headers && options.headers['Content-Type'] === 'application/json' && body) options.body = JSON.stringify(body);
|
||||
|
||||
const response = await fetch(url, options);
|
||||
return parseResponse(response);
|
||||
};
|
||||
|
||||
export const capitalise = (str) => {
|
||||
const first = str[0].toUpperCase();
|
||||
return `${first}${str.substring(1)}`;
|
||||
|
Loading…
Reference in New Issue
Block a user