diff --git a/src/App.tsx b/src/App.tsx index a44abf9..ef9bc72 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,7 +11,7 @@ import { Tokens } from "./pages/Tokens"; function App() { return ( -
+
} /> diff --git a/src/authorization.ts b/src/authorization.ts index f600d39..c28d09c 100644 --- a/src/authorization.ts +++ b/src/authorization.ts @@ -88,6 +88,34 @@ export function authorizeLogin( }) } +export async function authFetch( + endpoint: string, + method: 'GET' | 'POST' | 'DELETE' | 'PATCH', + body?: any +): Promise { + const token = getTokenFromStorage() + + if (token) { + return await 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 + } + }) + } + + return null +} + export function useAuthFetch( endpoint: string, method: 'GET' | 'POST' | 'DELETE' | 'PATCH', @@ -96,31 +124,31 @@ export function useAuthFetch( 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), + authFetch(endpoint, method, body) + .then(setData) + .catch(err => { + console.error(err) }) - .then(response => { - if (response.status === 401) { - // clearTokenFromStorage() - return null - } else { - return response.json() as T - } - }) - .then(setData) - .catch(err => { - console.error(err) - }) - } }, []) return data } + +export function useAuthFetchRepeat( + endpoint: string, + method: 'GET' | 'POST' | 'DELETE' | 'PATCH', + body?: T +): [T | null, () => void] { + const [data, setData] = useState(null) + + const run = () => { + authFetch(endpoint, method, body) + .then(setData) + .catch(err => { + console.error(err) + }) + } + useEffect(run, []) + + return [data, run] +} diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index c8d2093..735edb3 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -1,36 +1,45 @@ -export function SVGRightArrow() { +export interface IconProps { + height?: string +} + +export function SVGRightArrow({ height = "1rem" }: IconProps) { return ( - + ) } -export function SVGEye() { +export function SVGEye({ height = "1.5rem" }: IconProps) { return ( - - - - - - - + + ) } -export function SVGHamburger() { +export function SVGHamburger({ height = "1.5em" }: IconProps) { return ( - - - - - - - - - - + + + + ) +} + +export function SVGPlus({ height = "2em" }: IconProps) { + return ( + + + + + ) +} + +export function SVGx({ height = "2em" }: IconProps) { + return ( + + + ) } \ No newline at end of file diff --git a/src/components/Inputs.tsx b/src/components/Inputs.tsx index 6bd0114..2693ab7 100644 --- a/src/components/Inputs.tsx +++ b/src/components/Inputs.tsx @@ -3,31 +3,39 @@ import { overrideTailwindClasses } from '../general' import { SVGEye } from "./Icons" interface InputProps extends Omit, "type"> { + altColor?: boolean invalid?: boolean } -export function InputText({ invalid, ...props }: InputProps) { - var inputRef = createRef() +export function InputText({ invalid, altColor, className, ...props }: InputProps) { return ( - ) } -export function InputPassword({ invalid, ...props }: InputProps) { +export function InputPassword({ invalid, altColor, className, ...props }: InputProps) { var inputRef = createRef() var [focused, setFocused] = useState(false) + var [interacting, setInteracting] = useState(false) var [showPassword, setShowPassword] = useState(false) var toggleShowPassword = () => { @@ -40,55 +48,64 @@ export function InputPassword({ invalid, ...props }: InputProps) { onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} className={overrideTailwindClasses(` - flex + inline-flex flex-row rounded-full px-3 py-1 mx-2 w-64 - bg-gray-800 + ${!altColor ? "bg-gray-800" : "bg-gray-700"} shadow-glow outline-none shadow-transparent ${invalid ? "outline-red-600 shadow-red-600" : ""} - ${focused ? "outline-blue-500 shadow-blue-500 bg-gray-700" : ""} + ${focused || interacting ? `outline-blue-500 shadow-blue-500 ${!altColor ? "bg-gray-800" : "bg-gray-600"}` : ""} transition-all + ${className ?? ""} `)}> - toggleShowPassword()}> + `)} /> + toggleShowPassword()} + onMouseDown={() => setInteracting(true)} + onMouseUp={() => setInteracting(false)}> ) } -interface ButtonProps extends HTMLAttributes { } +interface ButtonProps extends HTMLAttributes { + icon?: boolean +} -export function Button({ children, ...props }: ButtonProps) { +export function Button({ children, icon, className, ...props }: ButtonProps) { return ( - diff --git a/src/pages/Bots.tsx b/src/pages/Bots.tsx index 5dfa896..cf40f13 100644 --- a/src/pages/Bots.tsx +++ b/src/pages/Bots.tsx @@ -1,10 +1,153 @@ -import { useAuthFetch } from "../authorization" +import { useEffect, useMemo, useState } from "react" +import { Link } from "react-router-dom" +import { authFetch, useAuthFetch, useAuthFetchRepeat } from "../authorization" +import { SVGPlus } from "../components/Icons" +import { Button, InputPassword, InputText } from "../components/Inputs" +import { overrideTailwindClasses } from "../general" + +interface DiscordBot { + id: string + username: string + discriminator: string + avatar: string + bot: boolean + verified: boolean +} + +function BotContainer({ children, ...props }: { children: React.ReactNode } & React.HTMLAttributes) { + return ( +
+
+ {children} +
+
+ ) +} + +function Bot({ bot }: { bot: DiscordBot }) { + console.log(bot) + return ( + + + +
+ {bot.username} + #{bot.discriminator} +
+
+ X Assigned tokens +
+
+ + ) +} + +function AddBot({ onClick = () => { } }: { onClick?: () => void }) { + return ( + onClick()}> +
+ +
+
+ ) +} + +function AddBotModal({ visible, onClose }: { visible: boolean, onClose: () => void }) { + const [token, setToken] = useState("") + const [invalid, setInvalid] = useState(false) + + useMemo(() => { + setToken("") + setInvalid(false) + }, [visible]) + + useMemo(() => { + setInvalid(false) + }, [token]) + + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault() + const success = await authFetch<{ success: boolean }>("/bot", "POST", { token }) + if (success?.success) { + onClose() + } else { + setInvalid(true) + } + } + + return ( +
+
onClose()} /> +
+
+ Bot Token +
+ setToken(e.target.value)} /> + +
+ + ) +} export function Bots() { - var data = useAuthFetch("/bots", "GET") + const [bots, fetch] = useAuthFetchRepeat("/bots", "GET") + const [addingBot, setAddingBot] = useState(false) + + var manyBots = useMemo(() => { + if (!bots) return null + let b = bots.slice() + for (let i = 0; i < 10; i++) b.push(b[0]) + return b + }, [bots]) + return (
- {JSON.stringify(data)} + {bots && bots.map((bot, i) => )} + setAddingBot(true)} /> + { + setAddingBot(false) + fetch() + }} />
) } \ No newline at end of file diff --git a/src/pages/Layout.tsx b/src/pages/Layout.tsx index ec1e796..f617175 100644 --- a/src/pages/Layout.tsx +++ b/src/pages/Layout.tsx @@ -37,14 +37,15 @@ export function Layout() { fixed top-0 left-0 h-full - overflow-hidden + overflow-x-hidden + overflow-y-auto bg-gray-700 text-gray-400 ${collapsed ? "w-14" : "w-48"} `}> - -
+
Dashboard Bots Tokens diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 20cc255..345bbcc 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -36,7 +36,7 @@ export function Login() { onChange={e => setPassword(e.target.value)} />
-
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 810f364..4575e27 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,7 +1,10 @@ +import { LoginTokensMini } from "./settings/LoginTokensMini"; + export function Settings() { return (
settings +
) } \ No newline at end of file diff --git a/src/pages/settings/LoginTokensMini.tsx b/src/pages/settings/LoginTokensMini.tsx new file mode 100644 index 0000000..b470476 --- /dev/null +++ b/src/pages/settings/LoginTokensMini.tsx @@ -0,0 +1,31 @@ +import { useAuthFetch } from "../../authorization" + +interface LoginToken { + id: string + ip: string + end: string + user_agent: UserAgent + created_at: string + last_login: string +} + +interface UserAgent { + name: string + version: string + os: string + os_version: string + mobile: boolean + tablet: boolean + desktop: boolean +} + +export function LoginTokensMini() { + const tokens = useAuthFetch("/login/tokens", "GET") + return tokens && ( +
+ + {tokens.length} Login Token{tokens.length === 1 ? "" : "s"} + +
+ ) +} \ No newline at end of file