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 (
)
+}
+
+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