diff --git a/package.json b/package.json index 16f5d89..36e9725 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/App.js b/src/App.js index 4fb05d9..bf0a04e 100644 --- a/src/App.js +++ b/src/App.js @@ -55,7 +55,7 @@ function App() { : null} -
+
@@ -87,7 +87,10 @@ function App() { - + +
+

Made with ❤️ by Navy.gif  |  CSS by D3vision

+
diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js index 8788385..a1c31dc 100644 --- a/src/components/Sidebar.js +++ b/src/components/Sidebar.js @@ -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 {label}; + return {label}; }); elements.push(
- + {label}{subElements && } {subElements &&
@@ -39,11 +65,14 @@ const SidebarMenu = ({menuItems = [], children}) => { } return
+ -
{elements} {children} +
+ Log Out +
; }; diff --git a/src/css/App.css b/src/css/App.css index 36aa0d1..adf4922 100644 --- a/src/css/App.css +++ b/src/css/App.css @@ -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; diff --git a/src/css/components/Sidebar.css b/src/css/components/Sidebar.css index d1d9cd7..27976be 100644 --- a/src/css/components/Sidebar.css +++ b/src/css/components/Sidebar.css @@ -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; + } } \ No newline at end of file diff --git a/src/css/index.css b/src/css/index.css index 1c82b2a..765e0c4 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -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; } diff --git a/src/css/pages/Login.css b/src/css/pages/Login.css index 6a466f3..ce679fc 100644 --- a/src/css/pages/Login.css +++ b/src/css/pages/Login.css @@ -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; } \ No newline at end of file diff --git a/src/pages/Home.js b/src/pages/Home.js index ec87bf9..c1376be 100644 --- a/src/pages/Home.js +++ b/src/pages/Home.js @@ -35,7 +35,7 @@ const Profile = () => { return
-
+

Profile

@@ -48,7 +48,7 @@ const Profile = () => {
-
+

Settings

ID: {user.id}

diff --git a/src/pages/Login.js b/src/pages/Login.js index acc5199..695598f 100644 --- a/src/pages/Login.js +++ b/src/pages/Login.js @@ -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
+
@@ -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
- {fail ?

Invalid code

: null} -

Verification code

- - + return
+ +
+ +
+ +
+

Verification Code

+ {fail ?

Invalid code

: null} + + +
; }; diff --git a/src/pages/Register.js b/src/pages/Register.js index 017741d..8a2e4d9 100644 --- a/src/pages/Register.js +++ b/src/pages/Register.js @@ -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
+
diff --git a/yarn.lock b/yarn.lock index 5553adf..a998e3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"