- 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:
D3visionNL 2022-11-25 03:02:28 +01:00
parent e2cf9939f5
commit 782464b611
11 changed files with 235 additions and 44 deletions

View File

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

View File

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

View File

@ -1,6 +1,7 @@
.sidebar { .sidebar {
min-width: 200px;
height: 100%; height: 100%;
position: fixed;
width: 230px;
} }
.sidebar-menu { .sidebar-menu {

View File

@ -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{
@ -577,4 +585,53 @@ tbody tr:hover {
.ml-auto, .ml-auto,
.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;
} }

View File

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

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

View File

@ -52,4 +52,26 @@ 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;
} }

View File

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

View File

@ -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&apos;t ave an account? <a onClick={() => navigate('/register')} className="clickable">Register</a> instead Don&apos;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 />} />

View File

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

View File

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