From 8b06077359928217911d7742c410b272a9689be1 Mon Sep 17 00:00:00 2001 From: zomo Date: Sun, 11 Dec 2022 15:16:21 -0600 Subject: [PATCH] more pages --- src/App.tsx | 15 +++++-- src/authorization.ts | 88 +++++++++++++++++++++++++++++++++++++---- src/index.scss | 80 ++++++++++++++++++++++++++++++++++++- src/pages/404.tsx | 7 ++++ src/pages/Bot.tsx | 12 ++++++ src/pages/Bots.tsx | 10 +++++ src/pages/Dashboard.tsx | 7 ++++ src/pages/Layout.tsx | 28 ++++++++++--- src/pages/Login.tsx | 25 ++++++++---- src/pages/Settings.tsx | 7 ++++ src/pages/Token.tsx | 12 ++++++ src/pages/Tokens.tsx | 10 +++++ 12 files changed, 278 insertions(+), 23 deletions(-) create mode 100644 src/pages/404.tsx create mode 100644 src/pages/Bot.tsx create mode 100644 src/pages/Bots.tsx create mode 100644 src/pages/Dashboard.tsx create mode 100644 src/pages/Settings.tsx create mode 100644 src/pages/Token.tsx create mode 100644 src/pages/Tokens.tsx diff --git a/src/App.tsx b/src/App.tsx index 773e991..9b01577 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,20 +1,29 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { MissingPage } from "./pages/404"; +import { Bot } from "./pages/Bot"; +import { Bots } from "./pages/Bots"; +import { Dashboard } from "./pages/Dashboard"; import { Layout } from "./pages/Layout"; import { Login } from "./pages/Login"; +import { Settings } from "./pages/Settings"; +import { Token } from "./pages/Token"; +import { Tokens } from "./pages/Tokens"; function App() { return (
+ } /> } /> - }> {/* collapsible sidebar with links */} - {/* } /> + }> + } /> } /> } /> } /> } /> - } /> */} + } /> + } /> diff --git a/src/authorization.ts b/src/authorization.ts index be7dd6b..8e24b46 100644 --- a/src/authorization.ts +++ b/src/authorization.ts @@ -1,25 +1,58 @@ +import { useEffect, useState } from 'react' + export function getTokenFromStorage() { return localStorage.getItem('token') } -export enum LoggedIn { +export function clearTokenFromStorage() { + localStorage.removeItem('token') +} + +export enum LoginState { No, Yes, Invalid, } -export function isLoggedIn(): LoggedIn { +export function isLoggedIn(): LoginState { const token = getTokenFromStorage() if (token) { - return LoggedIn.Yes + return LoginState.Yes } - return LoggedIn.No + return LoginState.No +} + +export function useClearToken() { + const [loggedOut, setLoggedOut] = useState(false) + useEffect(() => { + clearTokenFromStorage() + setLoggedOut(true) + }, []) + return loggedOut +} + +export function usePersistentState( + key: string, + defaultData: T +): [T, (data: T) => void] { + const [data, setData] = useState(defaultData) + useEffect(() => { + const data = localStorage.getItem(key) + if (data) { + setData(JSON.parse(data)) + } + }, []) + const setPersistentData = (data: T) => { + localStorage.setItem(key, JSON.stringify(data)) + setData(data) + } + return [data, setPersistentData] } export function authorizeLogin( username: string, password: string, - setLoggedIn: (loggedIn: LoggedIn) => void + setLoggedIn: (loggedIn: LoginState) => void ) { // TODO why is ENV undefined? console.log(import.meta.env.API) @@ -40,9 +73,50 @@ export function authorizeLogin( .then(data => { if (data.token) { localStorage.setItem('token', data.token) - setLoggedIn(LoggedIn.Yes) + setLoggedIn(LoginState.Yes) } else { - setLoggedIn(LoggedIn.Invalid) + setLoggedIn(LoginState.Invalid) } }) + .catch(err => { + console.error(err) + setLoggedIn(LoginState.No) + }) +} + +export function useAuthFetch( + endpoint: string, + method: 'GET' | 'POST' | 'DELETE' | 'PATCH', + body?: T +): T | null { + const [data, setData] = useState(null) + + useEffect(() => { + const token = getTokenFromStorage() + + if (token) { + fetch('http://localhost:8080' + endpoint, { + method: method, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + token, + }, + body: JSON.stringify(body), + }) + .then(response => { + if (response.status === 401) { + clearTokenFromStorage() + return null + } else { + return response.json() as T + } + }) + .then(setData) + .catch(err => { + console.error(err) + }) + } + }, []) + + return data } diff --git a/src/index.scss b/src/index.scss index 650d490..fe79938 100644 --- a/src/index.scss +++ b/src/index.scss @@ -5,6 +5,7 @@ body { #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; @@ -46,7 +47,8 @@ body { } } - button { + button, .button { + display: inline-block; border: none; outline: none; background-color: var(--bg-color-2); @@ -77,6 +79,10 @@ body { fill: var(--text-color-1); } } + + &:active { + background-color: var(--bg-color-3-5); + } } #login { @@ -92,4 +98,76 @@ body { 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; + } } \ No newline at end of file diff --git a/src/pages/404.tsx b/src/pages/404.tsx new file mode 100644 index 0000000..f9bd3f6 --- /dev/null +++ b/src/pages/404.tsx @@ -0,0 +1,7 @@ +export function MissingPage() { + return ( +
+ 404 +
+ ) +} \ No newline at end of file diff --git a/src/pages/Bot.tsx b/src/pages/Bot.tsx new file mode 100644 index 0000000..1c8c6a8 --- /dev/null +++ b/src/pages/Bot.tsx @@ -0,0 +1,12 @@ +import { useParams } from "react-router-dom" +import { useAuthFetch } from "../authorization" + +export function Bot() { + const { id } = useParams() + var data = useAuthFetch("/bot/" + (id || ""), "GET") + return ( +
+ {JSON.stringify(data)} +
+ ) +} \ No newline at end of file diff --git a/src/pages/Bots.tsx b/src/pages/Bots.tsx new file mode 100644 index 0000000..5dfa896 --- /dev/null +++ b/src/pages/Bots.tsx @@ -0,0 +1,10 @@ +import { useAuthFetch } from "../authorization" + +export function Bots() { + var data = useAuthFetch("/bots", "GET") + return ( +
+ {JSON.stringify(data)} +
+ ) +} \ No newline at end of file diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx new file mode 100644 index 0000000..aa5b3ce --- /dev/null +++ b/src/pages/Dashboard.tsx @@ -0,0 +1,7 @@ +export function Dashboard() { + return ( +
+ dashboard +
+ ) +} \ No newline at end of file diff --git a/src/pages/Layout.tsx b/src/pages/Layout.tsx index 0ea7fb5..5cde7b8 100644 --- a/src/pages/Layout.tsx +++ b/src/pages/Layout.tsx @@ -1,14 +1,32 @@ -import { Outlet, Navigate } from 'react-router-dom' -import { isLoggedIn, LoggedIn } from '../authorization' +import { Outlet, Navigate, NavLink } from 'react-router-dom' +import { isLoggedIn, LoginState, usePersistentState } from '../authorization' + +const activeClassName: ({ isActive }: { isActive: boolean }) => string = ({ isActive }) => isActive ? "active" : "" export function Layout() { + const [collapsed, setCollapsed] = usePersistentState("collapsed", false) return (
{ - isLoggedIn() === LoggedIn.No && + isLoggedIn() === LoginState.No && } - Layout - + +
+ +
) } \ No newline at end of file diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index b796d27..a4401f1 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react" import { Navigate } from "react-router-dom" -import { authorizeLogin, isLoggedIn, LoggedIn } from "../authorization" +import { authorizeLogin, isLoggedIn, LoginState, useClearToken } from "../authorization" export function Login() { var [username, setUsername] = useState("") @@ -9,23 +9,23 @@ export function Login() { var login = () => { if (username.length === 0 || password.length === 0) { - setLoggedIn(LoggedIn.Invalid) + setLoggedIn(LoginState.Invalid) return } authorizeLogin(username, password, setLoggedIn) } useMemo(() => { - if (loggedIn === LoggedIn.Invalid) { - setLoggedIn(LoggedIn.No) + if (loggedIn === LoginState.Invalid) { + setLoggedIn(LoginState.No) } }, [username, password]) return (
- setUsername(e.target.value)} /> - setPassword(e.target.value)} /> + setUsername(e.target.value)} /> + setPassword(e.target.value)} />
{ - loggedIn === LoggedIn.Yes && + loggedIn === LoginState.Yes && }
) +} + +export function Logout() { + const loggedOut = useClearToken() + return ( + <> + { + loggedOut && + } + + ) } \ No newline at end of file diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx new file mode 100644 index 0000000..810f364 --- /dev/null +++ b/src/pages/Settings.tsx @@ -0,0 +1,7 @@ +export function Settings() { + return ( +
+ settings +
+ ) +} \ No newline at end of file diff --git a/src/pages/Token.tsx b/src/pages/Token.tsx new file mode 100644 index 0000000..a7c8b48 --- /dev/null +++ b/src/pages/Token.tsx @@ -0,0 +1,12 @@ +import { useParams } from "react-router-dom" +import { useAuthFetch } from "../authorization" + +export function Token() { + const { id } = useParams() + var data = useAuthFetch("/token/" + (id || ""), "GET") + return ( +
+ {JSON.stringify(data)} +
+ ) +} \ No newline at end of file diff --git a/src/pages/Tokens.tsx b/src/pages/Tokens.tsx new file mode 100644 index 0000000..93a64fe --- /dev/null +++ b/src/pages/Tokens.tsx @@ -0,0 +1,10 @@ +import { useAuthFetch } from "../authorization" + +export function Tokens() { + var data = useAuthFetch("/tokens", "GET") + return ( +
+ {JSON.stringify(data)} +
+ ) +} \ No newline at end of file