tailwindcss
This commit is contained in:
@@ -14,13 +14,16 @@
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"@vitejs/plugin-react": "^3.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"dotenv": "^16.0.3",
|
||||
"eslint": "^8.29.0",
|
||||
"postcss": "^8.4.20",
|
||||
"prettier": "^2.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.4.5",
|
||||
"sass": "^1.56.2",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.0.0"
|
||||
},
|
||||
|
||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import { Tokens } from "./pages/Tokens";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-gray-900 text-gray-200 w-full h-full">
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/logout" element={<Login />} />
|
||||
|
||||
81
src/_index.scss
Normal file
81
src/_index.scss
Normal 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
3
src/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
173
src/index.scss
173
src/index.scss
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
import './index.scss'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
|
||||
@@ -1,7 +1,27 @@
|
||||
import { Outlet, Navigate, NavLink } from 'react-router-dom'
|
||||
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() {
|
||||
const [collapsed, setCollapsed] = usePersistentState("collapsed", false)
|
||||
@@ -10,21 +30,36 @@ export function Layout() {
|
||||
{
|
||||
isLoggedIn() === LoginState.No && <Navigate to="/login" replace={true} />
|
||||
}
|
||||
<div id="sidebar" className={collapsed ? "collapse" : ""}>
|
||||
<label id="collapse">
|
||||
<input type="checkbox" checked={collapsed} onChange={e => setCollapsed(e.target.checked)} />
|
||||
<div className="button">yo</div>
|
||||
<div
|
||||
className={
|
||||
"fixed top-0 left-0 h-full overflow-hidden bg-gray-700 text-gray-400 "
|
||||
+ (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>
|
||||
<div className="menu">
|
||||
<NavLink className={activeClassName} to="/"><span className="item">Dashboard</span></NavLink>
|
||||
<NavLink className={activeClassName} to="/bots"><span className="item">Bots</span></NavLink>
|
||||
<NavLink className={activeClassName} to="/tokens"><span className="item">Tokens</span></NavLink>
|
||||
<NavLink className={activeClassName} to="/settings"><span className="item">Settings</span></NavLink>
|
||||
<NavLink className={activeClassName} to="/logout"><span className="item important">Logout</span></NavLink>
|
||||
<NavLink className={activeClassName} to="/404"><span className="item">(temp) 404</span></NavLink>
|
||||
<SLink to="/">Dashboard</SLink>
|
||||
<SLink to="/bots">Bots</SLink>
|
||||
<SLink to="/tokens">Tokens</SLink>
|
||||
<SLink to="/settings">Settings</SLink>
|
||||
<SLink important to="/logout">Logout</SLink>
|
||||
<SLink to="/404">(temp) 404</SLink>
|
||||
</div>
|
||||
</div>
|
||||
<div id="content">
|
||||
<div className={"w-full overflow-x-hidden " + (collapsed ? "pl-14" : "pl-48")}>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +1,59 @@
|
||||
import { useMemo, useState } from "react"
|
||||
import { createRef, useEffect, useMemo, useState } from "react"
|
||||
import { Navigate } from "react-router-dom"
|
||||
import { authorizeLogin, isLoggedIn, LoginState, useClearToken } from "../authorization"
|
||||
|
||||
export function Login() {
|
||||
var [username, setUsername] = 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) {
|
||||
setLoggedIn(LoginState.Invalid)
|
||||
setLoginState(LoginState.Invalid)
|
||||
return
|
||||
}
|
||||
authorizeLogin(username, password, setLoggedIn)
|
||||
authorizeLogin(username, password, setLoginState)
|
||||
}
|
||||
|
||||
useMemo(() => {
|
||||
if (loggedIn === LoginState.Invalid) {
|
||||
setLoggedIn(LoginState.No)
|
||||
if (loginState === LoginState.Invalid) {
|
||||
setLoginState(LoginState.No)
|
||||
}
|
||||
}, [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 (
|
||||
<div id="login">
|
||||
<div className="form">
|
||||
<input type="text" className={loggedIn === LoginState.Invalid ? "error" : ""} onChange={e => setUsername(e.target.value)} />
|
||||
<input type="password" className={loggedIn === LoginState.Invalid ? "error" : ""} onChange={e => setPassword(e.target.value)} />
|
||||
<div className="flex w-full h-full justify-center items-center align-middle">
|
||||
<form onSubmit={login}>
|
||||
<div className="flex justify-center items-center align-middle my-2">
|
||||
<input type="text" ref={usernameRef} onChange={e => setUsername(e.target.value)}
|
||||
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" />
|
||||
<input type="password" ref={passwordRef} onChange={e => setPassword(e.target.value)}
|
||||
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>
|
||||
<div>
|
||||
<button className="pad svg" onClick={login}>
|
||||
<svg viewBox="0 0 1000 1000">
|
||||
<div className="flex justify-center items-center align-middle my-2">
|
||||
<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">
|
||||
<svg viewBox="0 0 1000 1000" height="16px">
|
||||
<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 >
|
||||
)
|
||||
|
||||
23
tailwind.config.js
Normal file
23
tailwind.config.js
Normal 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: [],
|
||||
}
|
||||
Reference in New Issue
Block a user