I did a thing
- Fixed random things. - Added a footer. - Changed menu styling to be more pleasing. - Made menu turn into hamburger dropdown menu so this dashboard is somewhat okay to use on mobile. - Made a log out menu button for the mobile people. - Made the entire layout work on mobile. - Fixed the shit I broke along the way. It's 2:30am now, time for bed.
This commit is contained in:
parent
d9a9ecafbb
commit
2dfb996cde
@ -8,6 +8,7 @@
|
||||
"react-router": "^6.4.3",
|
||||
"react-router-dom": "^6.4.3",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-top-loading-bar": "^2.3.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -55,7 +55,7 @@ function App() {
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<div className='main-content'>
|
||||
<div className={`main-content ${user ? "" : "login"}`}>
|
||||
<ErrorBoundary>
|
||||
<Routes>
|
||||
|
||||
@ -87,7 +87,10 @@ function App() {
|
||||
|
||||
</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>
|
||||
|
||||
</BrowserRouter>
|
||||
|
@ -2,6 +2,9 @@ import React from 'react';
|
||||
import '../css/components/Sidebar.css';
|
||||
import NavLink from '../structures/NavLink';
|
||||
import logoImg from '../img/logo.png';
|
||||
import { useNavigate } from "react-router";
|
||||
import { useLoginContext } from "../structures/UserContext";
|
||||
import { clearSession, post } from "../util/Util";
|
||||
|
||||
const Sidebar = ({children}) => {
|
||||
|
||||
@ -15,9 +18,32 @@ const expandMenu = (event) => {
|
||||
event.preventDefault();
|
||||
event.target.parentElement.classList.toggle("open");
|
||||
};
|
||||
|
||||
const expandMobileMenu = (event) => {
|
||||
event.preventDefault();
|
||||
event.target.parentElement.parentElement.classList.toggle("show-menu");
|
||||
};
|
||||
|
||||
const closeMobileMenu = (event) => {
|
||||
if(event.target.classList.contains("sidebar-menu-item-carrot")) return;
|
||||
event.target.getRootNode().querySelector(".sidebar").classList.remove("show-menu");
|
||||
};
|
||||
|
||||
// Nav menu
|
||||
const SidebarMenu = ({menuItems = [], children}) => {
|
||||
const [user, updateUser] = useLoginContext();
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!user) return;
|
||||
|
||||
const logOut = async () => {
|
||||
const response = await post('/api/logout');
|
||||
if (response.status === 200) {
|
||||
clearSession();
|
||||
updateUser();
|
||||
navigate('/login');
|
||||
}
|
||||
};
|
||||
|
||||
const elements = [];
|
||||
for (const menuItem of menuItems) {
|
||||
@ -26,10 +52,10 @@ const SidebarMenu = ({menuItems = [], children}) => {
|
||||
let subElements = null;
|
||||
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' activeClassName='active' to={to} key={label}>{label}</NavLink>;
|
||||
return <NavLink className='sidebar-menu-item sidebar-menu-child-item' onClick={closeMobileMenu} activeClassName='active' to={to} key={label}>{label}</NavLink>;
|
||||
});
|
||||
elements.push(<div key={label} className='parent-menu'>
|
||||
<NavLink className={`sidebar-menu-item ${subElements?.length ? 'has-children' : ''}`} activeClassName='active' {...rest} key={label}>
|
||||
<NavLink className={`sidebar-menu-item ${subElements?.length ? 'has-children' : ''}`} onClick={closeMobileMenu} activeClassName='active' {...rest} key={label}>
|
||||
{label}{subElements && <i className="sidebar-menu-item-carrot" onClick={expandMenu}></i>}
|
||||
</NavLink>
|
||||
{subElements && <div className='sidebar-menu-child-wrapper'>
|
||||
@ -39,11 +65,14 @@ const SidebarMenu = ({menuItems = [], children}) => {
|
||||
}
|
||||
|
||||
return <div className='sidebar-menu'>
|
||||
<span className='hamburger' onClick={expandMobileMenu}></span>
|
||||
<img className="sidebar-logo" src={logoImg}/>
|
||||
<hr/>
|
||||
{elements}
|
||||
|
||||
{children}
|
||||
<div className="parent-menu log-out-menu-item">
|
||||
<a className="sidebar-menu-item" onClick={logOut} href="#">Log Out</a>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
};
|
||||
|
@ -22,12 +22,27 @@ header {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--background-secondary);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.page{
|
||||
padding: 0 15px 0 245px;
|
||||
}
|
||||
|
||||
.footer{
|
||||
padding: 15px 15px 15px 245px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px){
|
||||
.footer{
|
||||
padding: 15px;
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.ld{
|
||||
min-height: 140px;
|
||||
position: relative;
|
||||
|
@ -3,22 +3,29 @@
|
||||
position: fixed;
|
||||
width: 230px;
|
||||
padding: 1rem 0;
|
||||
z-index: 1000;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.sidebar-menu .parent-menu:first-of-type{
|
||||
border-top: 1px solid #232323;
|
||||
}
|
||||
|
||||
.parent-menu{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid #232323;
|
||||
}
|
||||
|
||||
.sidebar-logo{
|
||||
margin: auto;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
max-width: 170px;
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 3.5rem;
|
||||
max-height: 80px;
|
||||
}
|
||||
|
||||
.sidebar-menu-item-carrot{
|
||||
@ -33,15 +40,16 @@
|
||||
}
|
||||
.sidebar-menu-item.has-children.open .sidebar-menu-item-carrot{
|
||||
transform: rotate(-90deg);
|
||||
transition: transform ease-in-out 0.15s;
|
||||
transition: transform ease-in-out 0.2s;
|
||||
}
|
||||
.sidebar-menu-item.has-children.active .sidebar-menu-item-carrot{
|
||||
margin-right: 5px;
|
||||
}
|
||||
.sidebar-menu-item.has-children.open + .sidebar-menu-child-wrapper{
|
||||
height: 100%;
|
||||
opacity: 1;
|
||||
transition: max-height 0.5s ease-in-out, opacity 0.25s ease-in-out;
|
||||
/* opacity: 1; */
|
||||
/* transition: max-height 0.5s ease-in-out, opacity 0.25s ease-in-out; */
|
||||
transition: max-height ease-in-out 0.25s;
|
||||
max-height: 600px;
|
||||
}
|
||||
.sidebar-menu-item.sidebar-menu-item.has-children{
|
||||
@ -58,8 +66,9 @@
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
transition: max-height 0.25s ease-in-out, opacity 0.25s ease-in-out;
|
||||
/* opacity: 0; */
|
||||
/* transition: max-height 0.25s ease-in-out, opacity 0.25s ease-in-out; */
|
||||
transition: max-height ease-in-out 0.25s;
|
||||
}
|
||||
.sidebar hr{
|
||||
margin-left: 2rem;
|
||||
@ -67,7 +76,7 @@
|
||||
}
|
||||
.sidebar-menu-item {
|
||||
margin: 0px 0px 0px -20px;
|
||||
padding: 14px 0px 14px calc(35px + 2rem);
|
||||
padding: 15px 0px 15px calc(35px + 2rem);
|
||||
background-color: var(--dark-blue);
|
||||
color: var(--text-primary);
|
||||
border-radius: 6px;
|
||||
@ -102,4 +111,60 @@
|
||||
padding-left: 35px !important;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.hamburger{
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48px' height='48px' viewBox='0 0 48 48'%3E%3Ctitle%3E70 Basic icons by Xicons.co%3C/title%3E%3Cpath d='M41,14H7a2,2,0,0,1,0-4H41A2,2,0,0,1,41,14Z' fill='%23f5f5f5'/%3E%3Cpath d='M41,26H7a2,2,0,0,1,0-4H41A2,2,0,0,1,41,26Z' fill='%23f5f5f5'/%3E%3Cpath d='M41,38H7a2,2,0,0,1,0-4H41A2,2,0,0,1,41,38Z' fill='%23f5f5f5'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
right: 30px;
|
||||
margin-top: calc(0.75rem + 12px);
|
||||
margin-bottom: calc(1.75rem + 12px);
|
||||
cursor: pointer;
|
||||
}
|
||||
.hamburger:hover{
|
||||
opacity: 0.8;
|
||||
}
|
||||
.log-out-menu-item{
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
@media screen and (max-width: 768px){
|
||||
.page{
|
||||
padding-left: 15px;
|
||||
}
|
||||
.sidebar{
|
||||
width: 100%;
|
||||
max-height: 95px;
|
||||
overflow: hidden;
|
||||
transition: max-height ease-in-out 0.25s;
|
||||
}
|
||||
.sidebar.show-menu{
|
||||
max-height: 100vh;
|
||||
transition: max-height ease-in-out 0.25s;
|
||||
}
|
||||
.sidebar-logo{
|
||||
max-height: 60px;
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 1.75rem;
|
||||
margin-left: 30px;
|
||||
max-width: 82vw;
|
||||
}
|
||||
.hamburger{
|
||||
display: block;
|
||||
visibility: visible;
|
||||
}
|
||||
.main-content{
|
||||
margin-top: 95px;
|
||||
}
|
||||
header{
|
||||
display: none;
|
||||
}
|
||||
.log-out-menu-item{
|
||||
display: flex;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
@ -34,7 +34,9 @@ a:hover:not(.button){
|
||||
opacity: 1 !important;
|
||||
color: #C4C4C4;
|
||||
}
|
||||
|
||||
a:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
details[open] summary ~ * {
|
||||
animation: sweep .15s ease-in-out;
|
||||
}
|
||||
|
@ -25,4 +25,14 @@ input{
|
||||
.registerText{
|
||||
line-height: 1.6em;
|
||||
padding: 18px 10px 4px 10px;
|
||||
}
|
||||
.footer.login{
|
||||
padding: 15px !important;
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
.main-content.login{
|
||||
margin-top: 0;
|
||||
}
|
@ -35,7 +35,7 @@ const Profile = () => {
|
||||
|
||||
return <div className="row">
|
||||
|
||||
<div className="col-6">
|
||||
<div className="col-6-lg col-12">
|
||||
|
||||
<h3>Profile</h3>
|
||||
|
||||
@ -48,7 +48,7 @@ const Profile = () => {
|
||||
|
||||
</div>
|
||||
|
||||
<div className="col-6">
|
||||
<div className="col-6-lg col-12">
|
||||
<h3>Settings</h3>
|
||||
|
||||
<p><b>ID:</b> {user.id}</p>
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useRef } from "react";
|
||||
import { Route, Routes, useNavigate } from "react-router";
|
||||
import '../css/pages/Login.css';
|
||||
import logoImg from '../img/logo.png';
|
||||
import { useLoginContext } from "../structures/UserContext";
|
||||
import LoadingBar from 'react-top-loading-bar';
|
||||
import { fetchUser, post } from "../util/Util";
|
||||
|
||||
const loginMethods = [
|
||||
@ -18,9 +19,10 @@ const CredentialFields = () => {
|
||||
const [, setUser] = useLoginContext();
|
||||
const [fail, setFail] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const ref = useRef(null);
|
||||
|
||||
const loginClick = async (event) => {
|
||||
|
||||
ref.current.continuousStart();
|
||||
event.preventDefault();
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
@ -29,20 +31,23 @@ const CredentialFields = () => {
|
||||
const result = await post('/api/login', { username, password });
|
||||
|
||||
if (![200, 302].includes(result.status)) {
|
||||
ref.current.complete();
|
||||
setFail(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.data?.twoFactor) {
|
||||
setUser(await fetchUser());
|
||||
ref.current.complete();
|
||||
return navigate('/home');
|
||||
}
|
||||
|
||||
ref.current.complete();
|
||||
return navigate('verify');
|
||||
|
||||
};
|
||||
|
||||
return <div>
|
||||
<LoadingBar color='#2685ff' ref={ref} />
|
||||
<div className="is-center">
|
||||
<img className="logoImg mb-4" src={logoImg}></img>
|
||||
</div>
|
||||
@ -85,24 +90,34 @@ const TwoFactor = () => {
|
||||
const [fail, setFail] = useState(false);
|
||||
const [, setUser] = useLoginContext();
|
||||
const navigate = useNavigate();
|
||||
const ref = useRef(null);
|
||||
|
||||
const twoFactorClick = async () => {
|
||||
const code = document.getElementById('2faCode').value;
|
||||
if (!code) return;
|
||||
|
||||
ref.current.continuousStart();
|
||||
const result = await post('/api/login/verify', { code });
|
||||
if (result.status === 200) {
|
||||
setUser(await fetchUser());
|
||||
ref.current.complete();
|
||||
return navigate('/home');
|
||||
}
|
||||
ref.current.complete();
|
||||
setFail(true);
|
||||
};
|
||||
|
||||
return <div className="card mb-3 is-center dir-column">
|
||||
{fail ? <p>Invalid code</p> : null}
|
||||
<h2>Verification code</h2>
|
||||
<input autoComplete='off' placeholder='Code' required id='2faCode' type='password' />
|
||||
<button onClick={twoFactorClick}>Enter</button>
|
||||
return <div>
|
||||
<LoadingBar color='#2685ff' ref={ref} />
|
||||
<div className="is-center">
|
||||
<img className="logoImg mb-4" src={logoImg}></img>
|
||||
</div>
|
||||
|
||||
<div className="card mb-3 is-center dir-column">
|
||||
<h2>Verification Code</h2>
|
||||
{fail ? <p className="mt-0">Invalid code</p> : null}
|
||||
<input autoComplete='off' placeholder='Your 2FA code...' required id='2faCode' type='password' />
|
||||
<button className="button primary m-3" onClick={twoFactorClick}>Enter</button>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
@ -1,32 +1,41 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useRef } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { post } from "../util/Util";
|
||||
import '../css/pages/Register.css';
|
||||
import logoImg from '../img/logo.png';
|
||||
import LoadingBar from 'react-top-loading-bar';
|
||||
|
||||
const Register = () => {
|
||||
|
||||
const [error, setError] = useState();
|
||||
const [params] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const ref = useRef(null);
|
||||
|
||||
document.body.classList.add('bg-triangles');
|
||||
const code = params.get('code');
|
||||
|
||||
const submit = async (event) => {
|
||||
|
||||
ref.current.continuousStart();
|
||||
event.preventDefault();
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
if (!username.length || !password.length) return;
|
||||
|
||||
if (!username.length || !password.length) {
|
||||
ref.current.complete();
|
||||
return;
|
||||
}
|
||||
const response = await post('/api/register', { username, password, code });
|
||||
if (response.status !== 200) return setError(response.message);
|
||||
if (response.status !== 200) {
|
||||
ref.current.complete();
|
||||
return setError(response.message);
|
||||
}
|
||||
ref.current.complete();
|
||||
navigate('/login');
|
||||
|
||||
};
|
||||
|
||||
return <div className="row is-center is-full-screen is-marginless">
|
||||
<LoadingBar color='#2685ff' ref={ref} />
|
||||
<div className='registerWrapper col-6 col-6-md col-3-lg'>
|
||||
<div>
|
||||
<div className="is-center">
|
||||
|
@ -7496,6 +7496,11 @@ react-scripts@5.0.1:
|
||||
optionalDependencies:
|
||||
fsevents "^2.3.2"
|
||||
|
||||
react-top-loading-bar@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-top-loading-bar/-/react-top-loading-bar-2.3.1.tgz#d727eb6aaa412eae52a990e5de9f33e9136ac714"
|
||||
integrity sha512-rQk2Nm+TOBrM1C4E3e6KwT65iXyRSgBHjCkr2FNja1S51WaPulRA5nKj/xazuQ3x89wDDdGsrqkqy0RBIfd0xg==
|
||||
|
||||
react@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||
|
Loading…
Reference in New Issue
Block a user