Merge branch 'main' of git.zomo.dev:/zomo/discord-retokenizer-web

This commit is contained in:
2022-12-17 19:25:30 -06:00
10 changed files with 204 additions and 208 deletions

View File

@@ -14,13 +14,16 @@
"@types/react": "^18.0.26", "@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9", "@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^3.0.0", "@vitejs/plugin-react": "^3.0.0",
"autoprefixer": "^10.4.13",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"eslint": "^8.29.0", "eslint": "^8.29.0",
"postcss": "^8.4.20",
"prettier": "^2.8.1", "prettier": "^2.8.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.4.5", "react-router-dom": "^6.4.5",
"sass": "^1.56.2", "sass": "^1.56.2",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.4", "typescript": "^4.9.4",
"vite": "^4.0.0" "vite": "^4.0.0"
}, },

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -11,7 +11,7 @@ import { Tokens } from "./pages/Tokens";
function App() { function App() {
return ( return (
<div> <div className="bg-gray-900 text-gray-200 w-full h-full">
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
<Route path="/logout" element={<Login />} /> <Route path="/logout" element={<Login />} />

81
src/_index.scss Normal file
View File

@@ -0,0 +1,81 @@
#root {
--bg-color-1: #262626;
--bg-color-2: #363636;
--bg-color-3-5: #3d3d3d;
--bg-color-3: #464646;
--text-color-1: #d6d6d6;
--text-color-2: #a6a6a6;
--color-error: #860000;
#sidebar {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 200px;
& + #content {
margin-left: 200px;
}
&.collapse {
width: 50px;
& + #content {
margin-left: 50px;
}
}
background-color: var(--bg-color-2);
color: var(--text-color-2);
#collapse {
cursor: pointer;
input {
display: none;
}
}
.menu {
display: flex;
flex-direction: column;
align-items: center;
font-weight: bold;
width: 100%;
height: calc(100% - 50px);
a {
text-decoration: none;
color: inherit;
font-weight: inherit;
width: 100%;
}
.item {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 50px;
font-size: 16px;
&:hover,
&:focus {
background-color: var(--bg-color-3);
color: var(--text-color-1);
&.important {
background-color: var(--color-error);
}
}
}
}
}
#content {
overflow-x: hidden;
}
}

3
src/index.css Normal file
View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,173 +0,0 @@
body {
margin: 0;
}
#root {
--bg-color-1: #262626;
--bg-color-2: #363636;
--bg-color-3-5: #3d3d3d;
--bg-color-3: #464646;
--text-color-1: #d6d6d6;
--text-color-2: #a6a6a6;
--color-error: #860000;
background-color: var(--bg-color-1);
color: var(--text-color-1);
font-family: Helvetica, Arial, sans-serif;
font-size: 16px;
width: 100%;
height: 100%;
input {
border: none;
outline: none;
background-color: var(--bg-color-2);
color: var(--text-color-2);
font-size: 16px;
&[type="text"],
&[type="password"] {
width: 200px;
}
padding: 7px 10px;
margin: 0 5px;
border-radius: 200px;
&:focus {
background-color: var(--bg-color-3);
color: var(--text-color-1);
}
border-bottom: 1px solid transparent;
&.error {
border-bottom: 1px solid var(--color-error);
}
}
button, .button {
display: inline-block;
border: none;
outline: none;
background-color: var(--bg-color-2);
color: var(--text-color-2);
font-size: 16px;
&.svg svg {
fill: var(--text-color-2);
height: 16px;
}
padding: 7px 10px;
margin: 0 5px;
&.pad {
padding: 10px 15px;
margin: 10px;
}
border-radius: 200px;
&:hover,
&:focus {
background-color: var(--bg-color-3);
color: var(--text-color-1);
&.svg svg {
fill: var(--text-color-1);
}
}
&:active {
background-color: var(--bg-color-3-5);
}
}
#login {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
.form {
margin-bottom: 5px;
}
}
#sidebar {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 200px;
& + #content {
margin-left: 200px;
}
&.collapse {
width: 50px;
& + #content {
margin-left: 50px;
}
}
background-color: var(--bg-color-2);
color: var(--text-color-2);
#collapse {
cursor: pointer;
input {
display: none;
}
}
.menu {
display: flex;
flex-direction: column;
align-items: center;
font-weight: bold;
width: 100%;
height: calc(100% - 50px);
a {
text-decoration: none;
color: inherit;
font-weight: inherit;
width: 100%;
}
.item {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 50px;
font-size: 16px;
&:hover,
&:focus {
background-color: var(--bg-color-3);
color: var(--text-color-1);
&.important {
background-color: var(--color-error);
}
}
}
}
}
#content {
overflow-x: hidden;
}
}

View File

@@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import App from './App' import App from './App'
import './index.scss' import './index.css'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode> <React.StrictMode>

View File

@@ -1,7 +1,27 @@
import { Outlet, Navigate, NavLink } from 'react-router-dom' import { Outlet, Navigate, NavLink } from 'react-router-dom'
import { isLoggedIn, LoginState, usePersistentState } from '../authorization' import { isLoggedIn, LoginState, usePersistentState } from '../authorization'
const activeClassName: ({ isActive }: { isActive: boolean }) => string = ({ isActive }) => isActive ? "active" : "" function SLink({ to, children, important }: {
to: string,
children: React.ReactNode,
important?: boolean
}) {
return (
<NavLink
className={
({ isActive }) =>
"flex w-full h-10 justify-center items-center hover:text-gray-300 "
+ (isActive ? "bg-gray-500 text-gray-300 " : "")
+ (important ?
"hover:bg-red-900 active:bg-red-800" :
"hover:bg-gray-600 active:bg-gray-600"
)
}
to={to}>
{children}
</NavLink>
)
}
export function Layout() { export function Layout() {
const [collapsed, setCollapsed] = usePersistentState("collapsed", false) const [collapsed, setCollapsed] = usePersistentState("collapsed", false)
@@ -10,21 +30,36 @@ export function Layout() {
{ {
isLoggedIn() === LoginState.No && <Navigate to="/login" replace={true} /> isLoggedIn() === LoginState.No && <Navigate to="/login" replace={true} />
} }
<div id="sidebar" className={collapsed ? "collapse" : ""}> <div
<label id="collapse"> className={
<input type="checkbox" checked={collapsed} onChange={e => setCollapsed(e.target.checked)} /> "fixed top-0 left-0 h-full overflow-hidden bg-gray-700 text-gray-400 "
<div className="button">yo</div> + (collapsed ? "w-14" : "w-48")
}>
<label className="inline-block rounded-full outline-none focus:bg-gray-700 hover:bg-gray-700 active:bg-gray-800 fill-gray-400 focus:fill-gray-200 px-3 py-2 mx-2 shadow-glow shadow-transparent invalid:outline-red-600 invalid:shadow-red-600 focus:outline-blue-500 focus:shadow-blue-500 transition-all">
{/* TODO I COPIED THE ABOVE FROM LOGIN */}
<input className="hidden" type="checkbox" checked={collapsed} onChange={e => setCollapsed(e.target.checked)} />
<svg height="24px" viewBox="0 0 3000 3000">
<g transform="matrix(1.50981,0,0,1.2859,-338.198,86.7981)">
<path d="M2211,435.5C2211,349.677 2151.66,280 2078.56,280L356.438,280C283.344,280 224,349.677 224,435.5C224,521.323 283.344,591 356.438,591L2078.56,591C2151.66,591 2211,521.323 2211,435.5Z" />
</g>
<g transform="matrix(1.50981,0,0,1.2859,-338.198,939.991)">
<path d="M2211,435.5C2211,349.677 2151.66,280 2078.56,280L356.438,280C283.344,280 224,349.677 224,435.5C224,521.323 283.344,591 356.438,591L2078.56,591C2151.66,591 2211,521.323 2211,435.5Z" />
</g>
<g transform="matrix(-1.50981,1.84899e-16,-1.57477e-16,-1.2859,3338.2,2913.2)">
<path d="M2211,435.5C2211,349.677 2151.66,280 2078.56,280L356.438,280C283.344,280 224,349.677 224,435.5C224,521.323 283.344,591 356.438,591L2078.56,591C2151.66,591 2211,521.323 2211,435.5Z" />
</g>
</svg>
</label> </label>
<div className="menu"> <div className="menu">
<NavLink className={activeClassName} to="/"><span className="item">Dashboard</span></NavLink> <SLink to="/">Dashboard</SLink>
<NavLink className={activeClassName} to="/bots"><span className="item">Bots</span></NavLink> <SLink to="/bots">Bots</SLink>
<NavLink className={activeClassName} to="/tokens"><span className="item">Tokens</span></NavLink> <SLink to="/tokens">Tokens</SLink>
<NavLink className={activeClassName} to="/settings"><span className="item">Settings</span></NavLink> <SLink to="/settings">Settings</SLink>
<NavLink className={activeClassName} to="/logout"><span className="item important">Logout</span></NavLink> <SLink important to="/logout">Logout</SLink>
<NavLink className={activeClassName} to="/404"><span className="item">(temp) 404</span></NavLink> <SLink to="/404">(temp) 404</SLink>
</div> </div>
</div> </div>
<div id="content"> <div className={"w-full overflow-x-hidden " + (collapsed ? "pl-14" : "pl-48")}>
<Outlet /> <Outlet />
</div> </div>
</div> </div>

View File

@@ -1,43 +1,61 @@
import { useMemo, useState } from "react" import { createRef, useEffect, useMemo, useState } from "react"
import { Navigate } from "react-router-dom" import { Navigate } from "react-router-dom"
import { authorizeLogin, isLoggedIn, LoginState, useClearToken } from "../authorization" import { authorizeLogin, isLoggedIn, LoginState, useClearToken } from "../authorization"
export function Login() { export function Login() {
var [username, setUsername] = useState("") var [username, setUsername] = useState("")
var [password, setPassword] = useState("") var [password, setPassword] = useState("")
var [loggedIn, setLoggedIn] = useState(isLoggedIn()) var [loginState, setLoginState] = useState(isLoggedIn())
var login = () => { var usernameRef = createRef<HTMLInputElement>()
var passwordRef = createRef<HTMLInputElement>()
var login = (e: React.ChangeEvent<HTMLFormElement>) => {
e.preventDefault()
if (username.length === 0 || password.length === 0) { if (username.length === 0 || password.length === 0) {
setLoggedIn(LoginState.Invalid) setLoginState(LoginState.Invalid)
return return
} }
authorizeLogin(username, password, setLoggedIn) authorizeLogin(username, password, setLoginState)
} }
useMemo(() => { useMemo(() => {
if (loggedIn === LoginState.Invalid) { if (loginState === LoginState.Invalid) {
setLoggedIn(LoginState.No) setLoginState(LoginState.No)
} }
}, [username, password]) }, [username, password])
useEffect(() => {
if (loginState === LoginState.Invalid) {
usernameRef.current?.setCustomValidity("Invalid username or password")
passwordRef.current?.setCustomValidity("Invalid username or password")
} else {
usernameRef.current?.setCustomValidity("")
passwordRef.current?.setCustomValidity("")
}
}, [loginState])
return ( return (
<div id="login"> <div className="flex w-full h-full justify-center items-center align-middle">
<div className="form"> <form onSubmit={login}>
<input type="text" className={loggedIn === LoginState.Invalid ? "error" : ""} onChange={e => setUsername(e.target.value)} /> <div className="flex justify-center items-center align-middle my-2">
<input type="password" className={loggedIn === LoginState.Invalid ? "error" : ""} onChange={e => setPassword(e.target.value)} /> <input type="text" ref={usernameRef} onChange={e => setUsername(e.target.value)}
</div> className="rounded-full outline-none bg-gray-800 focus:bg-gray-700 text-gray-400 focus:text-gray-200 px-3 py-1 mx-2 shadow-glow shadow-transparent invalid:outline-red-600 invalid:shadow-red-600 focus:outline-blue-500 focus:shadow-blue-500 transition-all" />
<div> <input type="password" ref={passwordRef} onChange={e => setPassword(e.target.value)}
<button className="pad svg" onClick={login}> className="rounded-full outline-none bg-gray-800 focus:bg-gray-700 text-gray-400 focus:text-gray-200 px-3 py-1 mx-2 shadow-glow shadow-transparent invalid:outline-red-600 invalid:shadow-red-600 focus:outline-blue-500 focus:shadow-blue-500 transition-all" />
<svg viewBox="0 0 1000 1000"> </div>
<path d="M619.5,899.5l350-350c27.3-27.3,27.3-71.7,0-99l-350-350c-27.3-27.3-71.7-27.3-99,0c-27.3,27.3-27.3,71.7,0,99L751,430H80c-38.7,0-70,31.3-70,70c0,38.7,31.3,70,70,70h671L520.5,800.5C506.8,814.2,500,832.1,500,850c0,17.9,6.8,35.8,20.5,49.5C547.8,926.8,592.2,926.8,619.5,899.5z" /> <div className="flex justify-center items-center align-middle my-2">
</svg> <button className="rounded-full outline-none bg-gray-800 focus:bg-gray-700 hover:bg-gray-700 active:bg-gray-800 fill-gray-400 focus:fill-gray-200 px-3 py-2 shadow-glow shadow-transparent invalid:outline-red-600 invalid:shadow-red-600 focus:outline-blue-500 focus:shadow-blue-500 transition-all">
</button> <svg viewBox="0 0 1000 1000" height="16px">
</div> <path d="M619.5,899.5l350-350c27.3-27.3,27.3-71.7,0-99l-350-350c-27.3-27.3-71.7-27.3-99,0c-27.3,27.3-27.3,71.7,0,99L751,430H80c-38.7,0-70,31.3-70,70c0,38.7,31.3,70,70,70h671L520.5,800.5C506.8,814.2,500,832.1,500,850c0,17.9,6.8,35.8,20.5,49.5C547.8,926.8,592.2,926.8,619.5,899.5z" />
</svg>
</button>
</div>
</form>
{ {
loggedIn === LoginState.Yes && <Navigate to="/" replace={true} /> loginState === LoginState.Yes && <Navigate to="/" replace={true} />
} }
</div> </div >
) )
} }

23
tailwind.config.js Normal file
View File

@@ -0,0 +1,23 @@
/** @type {import('tailwindcss').Config} */
const colors = require('tailwindcss/colors')
module.exports = {
content: ["./src/**/*.{ts,tsx,scss,html}"],
theme: {
colors: {
transparent: 'transparent',
current: 'currentColor',
black: colors.black,
white: colors.white,
gray: colors.slate,
red: colors.red,
blue: colors.blue,
},
extend: {
boxShadow: {
'glow': '0 0 20px -10px rgba(0,0,0,.5)',
}
}
},
plugins: [],
}