summaryrefslogtreecommitdiff
path: root/front
diff options
context:
space:
mode:
Diffstat (limited to 'front')
-rw-r--r--front/src/index.css2
-rw-r--r--front/src/index.js5
-rw-r--r--front/src/root.jsx24
-rw-r--r--front/src/routes/bots.jsx194
-rw-r--r--front/src/routes/keys.jsx10
-rw-r--r--front/src/routes/man_pages.jsx6
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>
).