- Standardized button colors.
- Fixed register page. - Added copy button for One-Click Sign-Up. - Made sidebar fixed to the viewport (so it doesn't scroll with the page). - Added support for a loader class. - Cleaned up the Users page (added headers and hr's). - Fixed Admin page cause Navy broke it.
This commit is contained in:
parent
e2cf9939f5
commit
782464b611
@ -24,7 +24,7 @@ const UserControls = () => {
|
|||||||
}}>
|
}}>
|
||||||
<details ref={detailsRef} className='dropdown user-controls'>
|
<details ref={detailsRef} className='dropdown user-controls'>
|
||||||
<summary className="is-vertical-align">
|
<summary className="is-vertical-align">
|
||||||
Hello {user.displayName}
|
Hello {user.displayName || user.username}
|
||||||
<img className="profile-picture" src="https://cdn.discordapp.com/avatars/187613017733726210/ee764860975d78bfd52652c6ddaed47b.webp?size=64"></img>
|
<img className="profile-picture" src="https://cdn.discordapp.com/avatars/187613017733726210/ee764860975d78bfd52652c6ddaed47b.webp?size=64"></img>
|
||||||
</summary>
|
</summary>
|
||||||
|
|
||||||
|
@ -21,6 +21,69 @@ header {
|
|||||||
.main-content {
|
.main-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0 15px;
|
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
padding: 0 15px 0 245px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ld{
|
||||||
|
min-height: 140px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ld:before {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg version='1.1' id='L9' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 100 100' enable-background='new 0 0 0 0' xml:space='preserve'%3E%3Cpath fill='%23fff' d='M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50'%3E%3CanimateTransform attributeName='transform' attributeType='XML' type='rotate' dur='1s' from='0 50 50' to='360 50 50' repeatCount='indefinite' /%3E%3C/path%3E%3C/svg%3E");
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 65px;
|
||||||
|
height: 65px;
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all .3s ease-in-out;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
z-index: 49;
|
||||||
|
}
|
||||||
|
.ld:after{
|
||||||
|
content: "Loading...";
|
||||||
|
display: block;
|
||||||
|
background: var(--bg-secondary-color);
|
||||||
|
width: 99%;
|
||||||
|
height: 95%;
|
||||||
|
margin: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all .3s ease-in-out;
|
||||||
|
z-index: 48;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 45px;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
.ld.loading:before,.ld.loading:after{
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
@keyframes ld_rotation {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(359deg);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
.sidebar {
|
.sidebar {
|
||||||
min-width: 200px;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
width: 230px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-menu {
|
.sidebar-menu {
|
||||||
|
@ -18,6 +18,14 @@
|
|||||||
--font-family-mono: monaco, "Consolas", "Lucida Console", monospace;
|
--font-family-mono: monaco, "Consolas", "Lucida Console", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hr{
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-wrap{
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.clickable {
|
.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -37,7 +45,7 @@ details[open] summary ~ * {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main-content .card{
|
.main-content .card{
|
||||||
padding: 1rem 2rem 2rem 2rem;
|
padding: 1.5rem 2rem 1.5rem 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.hover tbody tr{
|
table.hover tbody tr{
|
||||||
@ -578,3 +586,52 @@ tbody tr:hover {
|
|||||||
.mx-auto {
|
.mx-auto {
|
||||||
margin-left: auto !important;
|
margin-left: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iconButton{
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconButton img{
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltip container */
|
||||||
|
.tooltip {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltip text */
|
||||||
|
.tooltip .tooltiptext {
|
||||||
|
visibility: hidden;
|
||||||
|
width: 120px;
|
||||||
|
background-color: black;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
/* Position the tooltip text - see examples below! */
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: -35px;
|
||||||
|
left: -43px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip .tooltiptext::before{
|
||||||
|
content: ' ';
|
||||||
|
border-left: 10px solid transparent;
|
||||||
|
border-right: 10px solid transparent;
|
||||||
|
border-top: 15px solid #000000;
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
right: 5px;
|
||||||
|
bottom: -5px;
|
||||||
|
margin-right: calc(50% - 13px);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the tooltip text when you mouse over the tooltip container */
|
||||||
|
.tooltip:focus .tooltiptext {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
@ -7,9 +7,22 @@ input{
|
|||||||
.logoImg{
|
.logoImg{
|
||||||
max-height: 120px;
|
max-height: 120px;
|
||||||
}
|
}
|
||||||
.dingus{
|
.loginWrapper{
|
||||||
max-width: 407px;
|
max-width: 407px;
|
||||||
}
|
}
|
||||||
.methodsWrapper{
|
.methodsWrapper{
|
||||||
gap: 1px 10px;
|
gap: 1px 10px;
|
||||||
}
|
}
|
||||||
|
.loginForm{
|
||||||
|
width: 77%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.loginForm .button{
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
.registerText{
|
||||||
|
line-height: 1.6em;
|
||||||
|
padding: 18px 10px 4px 10px;
|
||||||
|
}
|
25
src/css/pages/Register.css
Normal file
25
src/css/pages/Register.css
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
input{
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.logoImg{
|
||||||
|
max-height: 120px;
|
||||||
|
}
|
||||||
|
.registerWrapper{
|
||||||
|
max-width: 407px;
|
||||||
|
}
|
||||||
|
.methodsWrapper{
|
||||||
|
gap: 1px 10px;
|
||||||
|
}
|
||||||
|
.registerForm{
|
||||||
|
width: 77%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.registerForm .button{
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
.loginText{
|
||||||
|
line-height: 1.6em;
|
||||||
|
padding: 18px 10px 4px 10px;
|
||||||
|
}
|
@ -53,3 +53,25 @@ li input {
|
|||||||
.tree input{
|
.tree input{
|
||||||
width: 60px !important;
|
width: 60px !important;
|
||||||
}
|
}
|
||||||
|
.userActionButtons{
|
||||||
|
display:flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.userActionButtons .button+.button{
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
.registerCodeWrapper{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.registerCodeWrapper p{
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.userId{
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
line-height: initial;
|
||||||
|
}
|
@ -3,7 +3,7 @@ import '../css/pages/Admin.css';
|
|||||||
|
|
||||||
const Admin = () => {
|
const Admin = () => {
|
||||||
|
|
||||||
return <div className="user-page">
|
return <div className="page">
|
||||||
<h2 className="pageTitle">Admin</h2>
|
<h2 className="pageTitle">Admin</h2>
|
||||||
<div className='card'>
|
<div className='card'>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,14 +48,14 @@ const CredentialFields = () => {
|
|||||||
{fail ? <p>Invalid credentials</p> : null}
|
{fail ? <p>Invalid credentials</p> : null}
|
||||||
<h2>Log in</h2>
|
<h2>Log in</h2>
|
||||||
|
|
||||||
<form>
|
<form className="loginForm">
|
||||||
<input autoComplete='off' placeholder='Username' required id='username' type='text' autoFocus />
|
<input autoComplete='off' placeholder='Username' required id='username' type='text' autoFocus />
|
||||||
<input autoComplete='off' placeholder='Password' required id='password' type='password' />
|
<input autoComplete='off' placeholder='Password' required id='password' type='password' />
|
||||||
<input value='Enter' type='button' onClick={loginClick} />
|
<button className="button primary" onClick={loginClick}>Enter</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div>
|
<div className="text-center registerText">
|
||||||
Don't ave an account? <a onClick={() => navigate('/register')} className="clickable">Register</a> instead
|
Don't have an account?<br/><a onClick={() => navigate('/register')} className="clickable">Register</a> instead!
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -106,7 +106,7 @@ const Login = () => {
|
|||||||
document.body.classList.add('bg-triangles');
|
document.body.classList.add('bg-triangles');
|
||||||
|
|
||||||
return <div className="row is-center is-full-screen is-marginless">
|
return <div className="row is-center is-full-screen is-marginless">
|
||||||
<div className='dingus col-6 col-6-md col-3-lg'>
|
<div className='loginWrapper col-6 col-6-md col-3-lg'>
|
||||||
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/' element={<CredentialFields />} />
|
<Route path='/' element={<CredentialFields />} />
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { post } from "../util/Util";
|
import { post } from "../util/Util";
|
||||||
|
import '../css/pages/Register.css';
|
||||||
|
import logoImg from '../img/logo.png';
|
||||||
|
|
||||||
// TODO: Make this page look similar to the login page, probably generalise the components
|
// TODO: Make this page look similar to the login page, probably generalise the components
|
||||||
const Register = () => {
|
const Register = () => {
|
||||||
@ -23,27 +25,30 @@ const Register = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return <div className="row is-center is-full-screen is-marginless">
|
return <div className="row is-center is-full-screen is-marginless">
|
||||||
|
<div className='registerWrapper col-6 col-6-md col-3-lg'>
|
||||||
<div className='dingus col-6 col-6-md col-3-lg'>
|
<div>
|
||||||
<div className="card mb-3 is-center dir-column">
|
<div className="is-center">
|
||||||
<h2>Register</h2>
|
<img className="logoImg mb-4" src={logoImg}></img>
|
||||||
|
|
||||||
{error && <p>{error}</p>}
|
|
||||||
|
|
||||||
<form action="/api/user">
|
|
||||||
<input autoComplete='off' placeholder='Username' required id='username' type='text' autoFocus />
|
|
||||||
<input autoComplete='off' placeholder='Password' required id='password' type='password' />
|
|
||||||
<input onClick={submit} className="button" type='button' value='Register' />
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
Have an account? <a onClick={() => navigate('/login')} className="clickable">Log in</a> instead
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="card mb-3 is-center dir-column">
|
||||||
|
<h2>Register</h2>
|
||||||
|
|
||||||
|
{error && <p>{error}</p>}
|
||||||
|
|
||||||
|
<form className="registerForm" action="/api/user">
|
||||||
|
<input autoComplete='off' placeholder='Username' required id='username' type='text' autoFocus />
|
||||||
|
<input autoComplete='off' placeholder='Password' required id='password' type='password' />
|
||||||
|
<button className="button primary" onClick={submit}>Register</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="text-center registerText">
|
||||||
|
Have an account?<br/><a onClick={() => navigate('/login')} className="clickable">Log in</a> instead!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Register;
|
export default Register;
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { Route, Routes, useNavigate, useParams } from "react-router";
|
import { Route, Routes, useNavigate, useParams } from "react-router";
|
||||||
import ErrorBoundary from "../util/ErrorBoundary";
|
import ErrorBoundary from "../util/ErrorBoundary";
|
||||||
import { get } from '../util/Util';
|
import { get } from '../util/Util';
|
||||||
@ -114,16 +114,17 @@ const User = ({ user }) => {
|
|||||||
return <div className='user-card'>
|
return <div className='user-card'>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-6">
|
<div className="col-6-lg col-12">
|
||||||
<div className="flex is-vertical-align">
|
<div className="flex is-vertical-align flex-wrap">
|
||||||
<button className="button primary" onClick={() => {
|
<button className="button dark" onClick={() => {
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
}}>Back</button>
|
}}>Back</button>
|
||||||
<h2>User {user.id}</h2>
|
<h2 className="userId">User {user.id}</h2>
|
||||||
|
</div>
|
||||||
|
<div className="userActionButtons">
|
||||||
|
{user.disabled ? <button className="button success">Enable</button> : <button className="button error">Disable</button>}
|
||||||
|
<button className="button error">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{user.disabled ? <button>Enable</button> : <button>Disable</button>}
|
|
||||||
<button>Delete</button>
|
|
||||||
|
|
||||||
<p><b>Created: </b>{new Date(user.createdTimestamp).toDateString()}</p>
|
<p><b>Created: </b>{new Date(user.createdTimestamp).toDateString()}</p>
|
||||||
<p><b>2FA: </b>{user.twoFactor.toString()}</p>
|
<p><b>2FA: </b>{user.twoFactor.toString()}</p>
|
||||||
@ -148,7 +149,7 @@ const User = ({ user }) => {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-6">
|
<div className="col-6-lg col-12">
|
||||||
<Permissions perms={user.permissions} />
|
<Permissions perms={user.permissions} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -163,6 +164,7 @@ const User = ({ user }) => {
|
|||||||
const CreateUserField = () => {
|
const CreateUserField = () => {
|
||||||
|
|
||||||
const [link, setLink] = useState(null);
|
const [link, setLink] = useState(null);
|
||||||
|
const registerCode = useRef();
|
||||||
const getSignupCode = async () => {
|
const getSignupCode = async () => {
|
||||||
const response = await get('/api/register/code');
|
const response = await get('/api/register/code');
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
@ -171,16 +173,18 @@ const CreateUserField = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyToClipboard = (event) => {
|
const copyToClipboard = () => {
|
||||||
const text = event.target.innerText;
|
const text = registerCode.current.innerText;
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
};
|
};
|
||||||
|
|
||||||
return <div className="">
|
return <div>
|
||||||
|
<h4>One-Click Sign-Up</h4>
|
||||||
{link ?
|
{link ?
|
||||||
<span onClick={copyToClipboard}>{link}</span> :
|
<div className="registerCodeWrapper"><p ref={registerCode}>{link}</p><button className="button iconButton tooltip" onClick={copyToClipboard}><img src="https://www.svgrepo.com/show/256783/copy.svg"></img><span className="tooltiptext">Code copied!</span></button></div> :
|
||||||
<button onClick={getSignupCode} className="is-center">Create sign-up link</button>}
|
<button onClick={getSignupCode} className="button primary is-center">Create sign-up link</button>}
|
||||||
|
<hr/>
|
||||||
|
<h4>Create User</h4>
|
||||||
<form>
|
<form>
|
||||||
<label htmlFor="username">Username</label>
|
<label htmlFor="username">Username</label>
|
||||||
<input autoComplete="off" type='text' id='username' />
|
<input autoComplete="off" type='text' id='username' />
|
||||||
@ -218,7 +222,8 @@ const Users = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const UserListWrapper = () => {
|
const UserListWrapper = () => {
|
||||||
return <div className="user-list-wrapper row">
|
return <div className="user-list-wrapper row">
|
||||||
<div className="col-6">
|
<div className="col-6-lg col-12">
|
||||||
|
<h4>All Users</h4>
|
||||||
<Table headerItems={['Username', 'ID']}>
|
<Table headerItems={['Username', 'ID']}>
|
||||||
{users.map(user => <TableListEntry
|
{users.map(user => <TableListEntry
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -238,7 +243,7 @@ const Users = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-6">
|
<div className="col-6-lg col-12">
|
||||||
<CreateUserField />
|
<CreateUserField />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user