diff options
Diffstat (limited to 'front')
-rw-r--r-- | front/src/index.css | 2 | ||||
-rw-r--r-- | front/src/index.js | 5 | ||||
-rw-r--r-- | front/src/root.jsx | 24 | ||||
-rw-r--r-- | front/src/routes/bots.jsx | 194 | ||||
-rw-r--r-- | front/src/routes/keys.jsx | 10 | ||||
-rw-r--r-- | front/src/routes/man_pages.jsx | 6 |
6 files changed, 223 insertions, 18 deletions
diff --git a/front/src/index.css b/front/src/index.css index f9c4620..fc17142 100644 --- a/front/src/index.css +++ b/front/src/index.css @@ -69,7 +69,7 @@ body { .navbar { display: flex; flex-direction: row; - justify-content: space-between; + justify-content: center; align-items: center; margin-bottom: 1rem; diff --git a/front/src/index.js b/front/src/index.js index eb2801c..fdb7b5a 100644 --- a/front/src/index.js +++ b/front/src/index.js @@ -7,6 +7,7 @@ import { Root } from "./root"; import { Demo } from "./routes/demo"; import { Home } from "./routes/home"; import { Keys } from "./routes/keys"; +import { Bots } from "./routes/bots"; import { ManPages } from "./routes/man_pages"; import { Password } from "./routes/password"; import { AuthSuccessful } from "./routes/auth_successful"; @@ -37,6 +38,10 @@ const router = createBrowserRouter([ element: <AuthSuccessful />, }, { + path: "bots", + element: <Bots />, + }, + { path: "man-pages", element: <ManPages />, }, diff --git a/front/src/root.jsx b/front/src/root.jsx index 2b1e603..82e79c3 100644 --- a/front/src/root.jsx +++ b/front/src/root.jsx @@ -10,18 +10,18 @@ export const Root = () => { return ( <> <div className="container"> + <div className="flex-row-around"> + <Link to="/home"> + <img src={logo} className="logo" alt="CheSSH Logo" /> + </Link> + </div> <div className="navbar"> - <div className="flex-row-around"> - <Link to="/home"> - <img src={logo} className="logo" alt="CheSSH Logo" /> - </Link> - </div> <div className="nav"> - <Link className="link" to="/man-pages"> - Man Pages - </Link> {signedIn ? ( <> + <Link className="button" onClick={signOut} to="/"> + Sign Out + </Link> <Link className="link" to="/home"> Home </Link> @@ -31,8 +31,8 @@ export const Root = () => { <Link className="link" to="/keys"> Keys </Link> - <Link className="button" onClick={signOut} to="/"> - Sign Out + <Link className="link" to="/bots"> + Bots </Link> </> ) : ( @@ -45,6 +45,10 @@ export const Root = () => { </a> </> )} + + <Link className="link" to="/man-pages"> + Man Pages + </Link> </div> </div> <div className="content"> diff --git a/front/src/routes/bots.jsx b/front/src/routes/bots.jsx new file mode 100644 index 0000000..c2f1565 --- /dev/null +++ b/front/src/routes/bots.jsx @@ -0,0 +1,194 @@ +import Modal from "react-modal"; +import { useAuthContext } from "../context/auth_context"; +import { useEffect, useState } from "react"; + +Modal.setAppElement("#root"); + +const BotButton = ({ onSave, givenBot }) => { + const [open, setOpen] = useState(false); + const [name, setName] = useState(givenBot?.name || ""); + const [webhook, setWebhook] = useState(givenBot?.webhook || ""); + const [errors, setErrors] = useState(null); + const [isPublic, setIsPublic] = useState(givenBot?.public || false); + + const setDefaults = () => { + setName(""); + setWebhook(""); + setErrors(null); + }; + + const close = () => { + if (!givenBot) { + setDefaults(); + } + setOpen(false); + }; + + const updateBot = () => { + fetch(givenBot ? `/api/player/bots/${givenBot.id}` : "/api/player/bots", { + credentials: "same-origin", + method: givenBot ? "PUT" : "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + webhook: webhook.trim(), + name: name.trim(), + public: isPublic, + }), + }) + .then((r) => r.json()) + .then((d) => { + if (d.success) { + if (onSave) { + onSave(); + } + close(); + } else if (d.errors) { + if (typeof d.errors === "object") { + setErrors( + Object.keys(d.errors).map( + (field) => `${field}: ${d.errors[field].join(",")}` + ) + ); + } else { + setErrors([d.errors]); + } + } + }); + }; + + return ( + <div> + <button className="button" onClick={() => setOpen(true)}> + {givenBot ? "Update" : "+ Add"} Bot + </button> + {givenBot && ( + <> + <button + style={{ marginLeft: "1rem" }} + className="button gold" + onClick={() => { + navigator.clipboard.writeText(givenBot?.token); + alert("Bot's token was copied to the clipboard."); + }} + > + Copy Token + </button> + <button + style={{ marginLeft: "1rem" }} + className="button red" + onClick={() => + fetch(`/api/player/bots/${givenBot.id}/redrive`) + .then((r) => r.json()) + .then(({ message }) => alert(message)) + } + > + Schedule Redrive + </button> + </> + )} + <Modal + isOpen={open} + onRequestClose={close} + className="modal" + contentLabel="Add Bot" + > + <div style={{ minWidth: "20vw" }}> + <h3>Add Bot</h3> + <hr /> + <p>Bot Name *</p> + <input + style={{ width: "100%" }} + value={name} + onChange={(e) => setName(e.target.value)} + required + /> + </div> + <div> + <p>Webhook *</p> + <input + style={{ width: "100%" }} + value={webhook} + onChange={(e) => setWebhook(e.target.value)} + required + /> + </div> + <p> + Public *{" "} + <input + type="checkbox" + value={name} + checked={isPublic} + onChange={(e) => setIsPublic(!isPublic)} + required + /> + </p> + <div> + {errors && ( + <div style={{ color: "red" }}> + {errors.map((error, i) => ( + <p key={i}>{error}</p> + ))} + </div> + )} + </div> + <div className="flex-end-row"> + <button className="button" onClick={updateBot}> + {givenBot ? "Update" : "+ Add"} + </button> + <button className="button red" onClick={close}> + Cancel + </button> + </div> + </Modal> + </div> + ); +}; + +export const BotCard = ({ botStruct, onSave }) => { + const { name } = botStruct; + return ( + <div className="key-card"> + <h4>{name}</h4> + <BotButton onSave={onSave} givenBot={botStruct} /> + </div> + ); +}; + +export const Bots = () => { + const { + player: { id: userId }, + } = useAuthContext(); + const [bots, setBots] = useState(null); + + const refreshBots = () => + fetch("/api/player/bots") + .then((r) => r.json()) + .then((bots) => setBots(bots)); + + useEffect(() => { + if (userId) { + refreshBots(); + } + }, [userId]); + + if (bots === null) return <p>Loading...</p>; + + return ( + <> + <h1>Bots</h1> + <BotButton onSave={refreshBots} /> + + <div className="key-card-collection"> + {bots.length ? ( + bots.map((bot) => ( + <BotCard key={bot.id} onSave={refreshBots} botStruct={bot} /> + )) + ) : ( + <p>Looks like you've got no bots, try adding one!</p> + )} + </div> + </> + ); +}; diff --git a/front/src/routes/keys.jsx b/front/src/routes/keys.jsx index 5b50fa9..b9d4542 100644 --- a/front/src/routes/keys.jsx +++ b/front/src/routes/keys.jsx @@ -14,16 +14,14 @@ const minimizeKey = (key) => { return key; }; -const KeyCard = ({ onDelete, props }) => { - const { id, name, key } = props; - +const KeyCard = ({ onDelete, keyStruct: { id, name, key } }) => { const deleteThisKey = () => { if ( window.confirm( "Are you sure? This will close all your currently opened ssh sessions." ) ) { - fetch(`/api/keys/${id}`, { + fetch(`/api/player/keys/${id}`, { credentials: "same-origin", method: "DELETE", }) @@ -182,7 +180,7 @@ export const Keys = () => { } }, [userId, refreshKeys]); - if (!keys) return <p>Loading...</p>; + if (keys === null) return <p>Loading...</p>; if (Array.isArray(keys)) { return ( @@ -192,7 +190,7 @@ export const Keys = () => { <div className="key-card-collection"> {keys.length ? ( keys.map((key) => ( - <KeyCard key={key.id} onDelete={refreshKeys} props={key} /> + <KeyCard key={key.id} onDelete={refreshKeys} keyStruct={key} /> )) ) : ( <p>Looks like you've got no keys, try adding some!</p> diff --git a/front/src/routes/man_pages.jsx b/front/src/routes/man_pages.jsx index ea03c2a..f904394 100644 --- a/front/src/routes/man_pages.jsx +++ b/front/src/routes/man_pages.jsx @@ -74,7 +74,11 @@ export const ManPages = () => { <li> In the "Previous Games" viewer, use "m" to show the game's move history in UCI notation (which you may convert to PGN{" "} - <a href="https://www.dcode.fr/uci-chess-notation" target="_blank"> + <a + href="https://www.dcode.fr/uci-chess-notation" + target="_blank" + rel="noreferrer" + > here </a> ). |