From 144e4db7c76e4e5f47db3177825e4ad5e1b62697 Mon Sep 17 00:00:00 2001 From: Snazzah <7025343+Snazzah@users.noreply.github.com> Date: Mon, 30 Aug 2021 16:18:42 +0000 Subject: [PATCH 01/11] Initial bot page stylings --- src/context/intermediate/Intermediate.tsx | 1 + src/context/intermediate/Modals.tsx | 3 + .../intermediate/modals/TokenReveal.tsx | 27 +++++ src/pages/settings/panes/MyBots.tsx | 111 ++++++++++++++++-- src/pages/settings/panes/Panes.module.scss | 63 ++++++++++ 5 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 src/context/intermediate/modals/TokenReveal.tsx diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index e6a3eb49..a2e5b1ec 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -24,6 +24,7 @@ export type Screen = | { id: "signed_out" } | { id: "error"; error: string } | { id: "clipboard"; text: string } + | { id: "token_reveal"; token: string; username: string } | { id: "external_link_prompt"; link: string } | { id: "_prompt"; diff --git a/src/context/intermediate/Modals.tsx b/src/context/intermediate/Modals.tsx index 8815a16d..f8b57109 100644 --- a/src/context/intermediate/Modals.tsx +++ b/src/context/intermediate/Modals.tsx @@ -10,6 +10,7 @@ import { OnboardingModal } from "./modals/Onboarding"; import { PromptModal } from "./modals/Prompt"; import { SignedOutModal } from "./modals/SignedOut"; import {ExternalLinkModal} from "./modals/ExternalLinkPrompt"; +import { TokenRevealModal } from "./modals/TokenReveal"; export interface Props { screen: Screen; @@ -33,6 +34,8 @@ export default function Modals({ screen, openScreen }: Props) { return ; case "clipboard": return ; + case "token_reveal": + return ; case "onboarding": return ; case "external_link_prompt": diff --git a/src/context/intermediate/modals/TokenReveal.tsx b/src/context/intermediate/modals/TokenReveal.tsx new file mode 100644 index 00000000..5fa8a288 --- /dev/null +++ b/src/context/intermediate/modals/TokenReveal.tsx @@ -0,0 +1,27 @@ +import { Text } from "preact-i18n"; + +import Modal from "../../../components/ui/Modal"; + +interface Props { + onClose: () => void; + token: string; + username: string; +} + +export function TokenRevealModal({ onClose, token,username }: Props) { + return ( + , + }, + ]}> + {token} + + ); +} diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 5e558954..27e21083 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -1,17 +1,26 @@ +import { Key, Clipboard, Globe } from "@styled-icons/boxicons-regular"; +import { LockAlt } from "@styled-icons/boxicons-solid"; import { observer } from "mobx-react-lite"; import { Bot } from "revolt-api/types/Bots"; +import styled from "styled-components"; +import styles from "./Panes.module.scss"; +import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; +import { stopPropagation } from "../../../lib/stopPropagation"; + import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { useClient } from "../../../context/revoltjs/RevoltClient"; -import UserShort from "../../../components/common/user/UserShort"; +import Tooltip from "../../../components/common/Tooltip"; +import UserIcon from "../../../components/common/user/UserIcon"; import Button from "../../../components/ui/Button"; import Checkbox from "../../../components/ui/Checkbox"; import InputBox from "../../../components/ui/InputBox"; import Overline from "../../../components/ui/Overline"; import Tip from "../../../components/ui/Tip"; +import CategoryButton from "../../../components/ui/fluent/CategoryButton"; interface Data { _id: string; @@ -20,6 +29,21 @@ interface Data { interactions_url?: string; } +const BotBadge = styled.div` + display: inline-block; + + height: 1.3em; + padding: 0px 4px; + font-size: 0.7em; + user-select: none; + margin-inline-start: 2px; + text-transform: uppercase; + + color: var(--foreground); + background: var(--accent); + border-radius: calc(var(--border-radius) / 2); +`; + function BotEditor({ bot }: { bot: Data }) { const client = useClient(); const [data, setData] = useState(bot); @@ -78,10 +102,11 @@ export const MyBots = observer(() => { }, []); const [name, setName] = useState(""); - const { writeClipboard } = useIntermediate(); + const [editMode, setEditMode] = useState(false); + const { writeClipboard, openScreen } = useIntermediate(); return ( -
+
This section is under construction. @@ -113,16 +138,80 @@ export const MyBots = observer(() => { key={bot._id} style={{ background: "var(--secondary-background)", - margin: "8px", + margin: "8px 0", padding: "12px", }}> - -

- token:{" "} - - {bot.token} - -

+
+
+ + openScreen({ + id: "profile", + user_id: user!._id, + }) + } + /> +
+
+ {user!.username}{" "} + + + +
+ + +
+
+ + + {bot.public ? : } + + {/* */} +
+ } + onClick={() => writeClipboard(bot.token)} + description={ + <> + {"••••••••••••••••••••••••••••••••••••"}{" "} + + stopPropagation( + ev, + openScreen({ + id: "token_reveal", + token: bot.token, + username: user!.username, + }), + ) + }> + + + + } + action={}> + Token + Date: Mon, 30 Aug 2021 19:10:55 +0000 Subject: [PATCH 02/11] Add bot editing --- .vscode/settings.json | 5 +- src/components/ui/Checkbox.tsx | 15 +- src/pages/settings/panes/MyBots.tsx | 375 +++++++++++++-------- src/pages/settings/panes/Panes.module.scss | 7 + 4 files changed, 252 insertions(+), 150 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3734ef64..a0484054 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true -} \ No newline at end of file + "editor.formatOnSave": true, + "compile-hero.disable-compile-files-on-did-save-code": true +} diff --git a/src/components/ui/Checkbox.tsx b/src/components/ui/Checkbox.tsx index 8f52ac1b..1117a613 100644 --- a/src/components/ui/Checkbox.tsx +++ b/src/components/ui/Checkbox.tsx @@ -51,7 +51,7 @@ const CheckboxDescription = styled.span` color: var(--secondary-foreground); `; -const Checkmark = styled.div<{ checked: boolean }>` +const Checkmark = styled.div<{ checked: boolean; contrast?: boolean }>` margin: 4px; width: 24px; height: 24px; @@ -66,6 +66,16 @@ const Checkmark = styled.div<{ checked: boolean }>` color: var(--secondary-background); } + ${(props) => + props.contrast && + css` + background: var(--primary-background); + + svg { + color: var(--primary-background); + } + `} + ${(props) => props.checked && css` @@ -76,6 +86,7 @@ const Checkmark = styled.div<{ checked: boolean }>` export interface CheckboxProps { checked: boolean; disabled?: boolean; + contrast?: boolean; className?: string; children: Children; description?: Children; @@ -100,7 +111,7 @@ export default function Checkbox(props: CheckboxProps) { !props.disabled && props.onChange(!props.checked) } /> - + diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 27e21083..90b7838b 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -21,6 +21,7 @@ import InputBox from "../../../components/ui/InputBox"; import Overline from "../../../components/ui/Overline"; import Tip from "../../../components/ui/Tip"; import CategoryButton from "../../../components/ui/fluent/CategoryButton"; +import { User } from "revolt.js/dist/maps/Users"; interface Data { _id: string; @@ -29,6 +30,13 @@ interface Data { interactions_url?: string; } +interface Changes { + name?: string; + public?: boolean; + interactions_url?: string; + remove?: "InteractionsURL"; +} + const BotBadge = styled.div` display: inline-block; @@ -44,50 +52,212 @@ const BotBadge = styled.div` border-radius: calc(var(--border-radius) / 2); `; -function BotEditor({ bot }: { bot: Data }) { - const client = useClient(); - const [data, setData] = useState(bot); +interface Props { + bot: Bot; + user: User; + onDelete(): Promise; + onUpdate(changes: Changes): Promise; +} - function save() { - const changes: Record = {}; - if (data.username !== bot.username) changes.name = data.username; +function BotCard({ bot, user, onDelete, onUpdate }: Props) { + const [data, setData] = useState({ + _id: bot._id, + username: user!.username, + public: bot.public, + interactions_url: bot.interactions_url, + }); + const [saving, setSaving] = useState(false); + const [editMode, setEditMode] = useState(false); + const [usernameRef, setUsernameRef] = useState( + null, + ); + const [interactionsRef, setInteractionsRef] = + useState(null); + const { writeClipboard, openScreen } = useIntermediate(); + + async function save() { + const changes: Changes = {}; + if (data.username !== user!.username) changes.name = data.username; if (data.public !== bot.public) changes.public = data.public; - if (data.interactions_url !== bot.interactions_url) + if (data.interactions_url === '') + changes.remove = 'InteractionsURL'; + else if (data.interactions_url !== bot.interactions_url) changes.interactions_url = data.interactions_url; - - client.bots.edit(bot._id, changes); + setSaving(true); + try { + await onUpdate(changes); + setEditMode(false); + } catch (e) { + // TODO error handling + } + setSaving(false); } return ( -
-

- - setData({ ...data, username: e.currentTarget.value }) +

+
+
+ + openScreen({ + id: "profile", + user_id: user!._id, + }) + } + /> + {!editMode ? ( +
+
+ {user!.username}{" "} + + + +
+ + +
+ ) : ( + + setData({ + ...data, + username: e.currentTarget.value, + }) + } + /> + )} +
+ + {!editMode && ( + + {bot.public ? ( + + ) : ( + + )} + + )} + +
+ {!editMode && ( + } + onClick={() => writeClipboard(bot.token)} + description={ + <> + {"••••••••••••••••••••••••••••••••••••"}{" "} + + stopPropagation( + ev, + openScreen({ + id: "token_reveal", + token: bot.token, + username: user!.username, + }), + ) + }> + + + } - /> -

-

- setData({ ...data, public: v })}> - is public - -

-

interactions url: (reserved for the future)

-

- - setData({ - ...data, - interactions_url: e.currentTarget.value, - }) - } - /> -

- + action={}> + Token +
+ )} + {editMode && ( + <> + setData({ ...data, public: v })}> + Public Bot + +

Interactions URL

+
This field is reserved for the future.
+ + setData({ + ...data, + interactions_url: + e.currentTarget.value, + }) + } + /> + + )} + +
+ {editMode && ( + <> + + + + )} + {!editMode && ( + + )} +
); } @@ -102,8 +272,6 @@ export const MyBots = observer(() => { }, []); const [name, setName] = useState(""); - const [editMode, setEditMode] = useState(false); - const { writeClipboard, openScreen } = useIntermediate(); return (
@@ -132,116 +300,31 @@ export const MyBots = observer(() => {

my bots {bots?.map((bot) => { - const user = client.users.get(bot._id); + const user = client.users.get(bot._id)!; return ( -
-
-
- - openScreen({ - id: "profile", - user_id: user!._id, - }) - } - /> -
-
- {user!.username}{" "} - - - -
- - -
-
- - - {bot.public ? : } - - {/* */} -
- } - onClick={() => writeClipboard(bot.token)} - description={ - <> - {"••••••••••••••••••••••••••••••••••••"}{" "} - - stopPropagation( - ev, - openScreen({ - id: "token_reveal", - token: bot.token, - username: user!.username, - }), - ) - }> - - - - } - action={}> - Token - - - - -
+ .then(() => setBots(bots.filter((x) => x._id !== bot._id))) + + } + onUpdate={(changes: Changes) => + client.bots.edit(bot._id, changes).then(() => setBots( + bots.map((x) => { + if (x._id === bot._id) { + if ('public' in changes && typeof changes.public === 'boolean') x.public = changes.public; + if ('interactions_url' in changes) x.interactions_url = changes.interactions_url; + if (changes.remove === 'InteractionsURL') x.interactions_url = undefined; + } + return x; + }), + )) + } + /> ); })}
diff --git a/src/pages/settings/panes/Panes.module.scss b/src/pages/settings/panes/Panes.module.scss index 6550e5ca..116b7e8f 100644 --- a/src/pages/settings/panes/Panes.module.scss +++ b/src/pages/settings/panes/Panes.module.scss @@ -584,6 +584,13 @@ } } } + + .buttonRow { + margin-top: 2em; + display: flex; + flex-direction: row; + gap: 10px; + } } section { From 9a8acaa7f573cf4c2c7c0acab93061e04387886f Mon Sep 17 00:00:00 2001 From: Snazzah <7025343+Snazzah@users.noreply.github.com> Date: Tue, 31 Aug 2021 01:42:50 +0000 Subject: [PATCH 03/11] Bot avatar uploading --- src/pages/settings/panes/MyBots.tsx | 117 ++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 33 deletions(-) diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 90b7838b..f7e13b50 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -11,6 +11,7 @@ import { useEffect, useState } from "preact/hooks"; import { stopPropagation } from "../../../lib/stopPropagation"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; +import { FileUploader } from "../../../context/revoltjs/FileUploads"; import { useClient } from "../../../context/revoltjs/RevoltClient"; import Tooltip from "../../../components/common/Tooltip"; @@ -54,15 +55,16 @@ const BotBadge = styled.div` interface Props { bot: Bot; - user: User; - onDelete(): Promise; - onUpdate(changes: Changes): Promise; + onDelete(): void; + onUpdate(changes: Changes): void; } -function BotCard({ bot, user, onDelete, onUpdate }: Props) { +function BotCard({ bot, onDelete, onUpdate }: Props) { + const client = useClient(); + const [user, setUser] = useState(client.users.get(bot._id)!); const [data, setData] = useState({ _id: bot._id, - username: user!.username, + username: user.username, public: bot.public, interactions_url: bot.interactions_url, }); @@ -79,13 +81,13 @@ function BotCard({ bot, user, onDelete, onUpdate }: Props) { const changes: Changes = {}; if (data.username !== user!.username) changes.name = data.username; if (data.public !== bot.public) changes.public = data.public; - if (data.interactions_url === '') - changes.remove = 'InteractionsURL'; + if (data.interactions_url === "") changes.remove = "InteractionsURL"; else if (data.interactions_url !== bot.interactions_url) changes.interactions_url = data.interactions_url; setSaving(true); try { - await onUpdate(changes); + await client.bots.edit(bot._id, changes); + onUpdate(changes); setEditMode(false); } catch (e) { // TODO error handling @@ -93,6 +95,25 @@ function BotCard({ bot, user, onDelete, onUpdate }: Props) { setSaving(false); } + async function editBotAvatar(avatar?: string) { + setSaving(true); + await client.request("PATCH", "/users/id", { + headers: { "x-bot-token": bot.token }, + transformRequest: (data, headers) => { + // Remove user headers for this request + delete headers["x-user-id"]; + delete headers["x-session-token"]; + return data; + }, + data: JSON.stringify(avatar ? { avatar } : { remove: "Avatar" }), + }); + + const res = await client.bots.fetch(bot._id); + if (!avatar) res.user.update({}, "Avatar"); + setUser(res.user); + setSaving(false); + } + return (
- - openScreen({ - id: "profile", - user_id: user!._id, - }) - } - /> + {!editMode ? ( + + openScreen({ + id: "profile", + user_id: user._id, + }) + } + /> + ) : ( + editBotAvatar(avatar)} + remove={() => editBotAvatar()} + defaultPreview={user.generateAvatarURL( + { max_side: 256 }, + true, + )} + previewURL={user.generateAvatarURL( + { max_side: 256 }, + true, + )} + /> + )} + {!editMode ? (
@@ -165,6 +208,7 @@ function BotCard({ bot, user, onDelete, onUpdate }: Props) { )} @@ -300,29 +347,33 @@ export const MyBots = observer(() => {

my bots {bots?.map((bot) => { - const user = client.users.get(bot._id)!; return ( - client.bots - .delete(bot._id) - .then(() => setBots(bots.filter((x) => x._id !== bot._id))) - + setBots(bots.filter((x) => x._id !== bot._id)) } onUpdate={(changes: Changes) => - client.bots.edit(bot._id, changes).then(() => setBots( + setBots( bots.map((x) => { if (x._id === bot._id) { - if ('public' in changes && typeof changes.public === 'boolean') x.public = changes.public; - if ('interactions_url' in changes) x.interactions_url = changes.interactions_url; - if (changes.remove === 'InteractionsURL') x.interactions_url = undefined; + if ( + "public" in changes && + typeof changes.public === "boolean" + ) + x.public = changes.public; + if ("interactions_url" in changes) + x.interactions_url = + changes.interactions_url; + if ( + changes.remove === "InteractionsURL" + ) + x.interactions_url = undefined; } return x; }), - )) + ) } /> ); From b57d274ffce61ee9717541cea1f14e7b0a457123 Mon Sep 17 00:00:00 2001 From: Snazzah <7025343+Snazzah@users.noreply.github.com> Date: Tue, 31 Aug 2021 02:33:36 +0000 Subject: [PATCH 04/11] Add "add bot" button --- src/pages/settings/panes/MyBots.tsx | 30 ++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index f7e13b50..060d1639 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -2,6 +2,7 @@ import { Key, Clipboard, Globe } from "@styled-icons/boxicons-regular"; import { LockAlt } from "@styled-icons/boxicons-solid"; import { observer } from "mobx-react-lite"; import { Bot } from "revolt-api/types/Bots"; +import { User } from "revolt.js/dist/maps/Users"; import styled from "styled-components"; import styles from "./Panes.module.scss"; @@ -9,6 +10,7 @@ import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; import { stopPropagation } from "../../../lib/stopPropagation"; +import { internalEmit } from "../../../lib/eventEmitter"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { FileUploader } from "../../../context/revoltjs/FileUploads"; @@ -22,7 +24,6 @@ import InputBox from "../../../components/ui/InputBox"; import Overline from "../../../components/ui/Overline"; import Tip from "../../../components/ui/Tip"; import CategoryButton from "../../../components/ui/fluent/CategoryButton"; -import { User } from "revolt.js/dist/maps/Users"; interface Data { _id: string; @@ -297,12 +298,27 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { )} {!editMode && ( - + <> + + + )}
From 9bf6b3389531a0a6b8af1ba4545f8f9c3d5de6b0 Mon Sep 17 00:00:00 2001 From: Snazzah <7025343+Snazzah@users.noreply.github.com> Date: Tue, 31 Aug 2021 03:23:07 +0000 Subject: [PATCH 05/11] error handling & styling --- src/pages/settings/panes/MyBots.tsx | 37 +++++++++++++++------- src/pages/settings/panes/Panes.module.scss | 20 ++++++++++-- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 060d1639..22d5d125 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -9,8 +9,8 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useEffect, useState } from "preact/hooks"; -import { stopPropagation } from "../../../lib/stopPropagation"; import { internalEmit } from "../../../lib/eventEmitter"; +import { stopPropagation } from "../../../lib/stopPropagation"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { FileUploader } from "../../../context/revoltjs/FileUploads"; @@ -69,6 +69,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { public: bot.public, interactions_url: bot.interactions_url, }); + const [error, setError] = useState(""); const [saving, setSaving] = useState(false); const [editMode, setEditMode] = useState(false); const [usernameRef, setUsernameRef] = useState( @@ -86,18 +87,29 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { else if (data.interactions_url !== bot.interactions_url) changes.interactions_url = data.interactions_url; setSaving(true); + setError(""); try { await client.bots.edit(bot._id, changes); onUpdate(changes); setEditMode(false); } catch (e) { - // TODO error handling + if (e.isAxiosError && e.response.data?.type) { + switch (e.response.data.type) { + case "UsernameTaken": + setError("That username is taken!"); + break; + default: + setError(`Error: ${e.response.data.type}`); + break; + } + } else setError(e.toString()); } setSaving(false); } async function editBotAvatar(avatar?: string) { setSaving(true); + setError(""); await client.request("PATCH", "/users/id", { headers: { "x-bot-token": bot.token }, transformRequest: (data, headers) => { @@ -116,13 +128,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { } return ( -
+
{!editMode ? ( @@ -220,6 +226,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { }); usernameRef!.value = user!.username; interactionsRef!.value = bot.interactions_url || ""; + setError(""); setEditMode(false); } else setEditMode(true); }} @@ -255,7 +262,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { )} {editMode && ( - <> +
- +
+ )} + + {error && ( +
+ + {error} + +
)}
diff --git a/src/pages/settings/panes/Panes.module.scss b/src/pages/settings/panes/Panes.module.scss index 116b7e8f..dc3c751c 100644 --- a/src/pages/settings/panes/Panes.module.scss +++ b/src/pages/settings/panes/Panes.module.scss @@ -524,8 +524,25 @@ } .myBots { + + .botCard { + background: var(--secondary-background); + margin: 8px 0; + padding: 12px; + + h5 { margin-bottom: 1em } + } + + .botSection { + margin: 20px 0; + display: flex; + flex-direction: column; + gap: 5px; + + label { margin-top: 0 } + } + .infoheader { - margin-bottom: 15px; gap: 8px; width: 100%; padding: 6px 5px; @@ -586,7 +603,6 @@ } .buttonRow { - margin-top: 2em; display: flex; flex-direction: row; gap: 10px; From 0e4f73cfada366d3b9e22455b582284067fdfaba Mon Sep 17 00:00:00 2001 From: Snazzah <7025343+Snazzah@users.noreply.github.com> Date: Tue, 31 Aug 2021 04:00:45 +0000 Subject: [PATCH 06/11] Move creating bots to a popover --- src/context/intermediate/Intermediate.tsx | 2 + src/context/intermediate/Popovers.tsx | 4 + .../intermediate/popovers/CreateBot.tsx | 80 +++++++++++++++++++ src/pages/settings/panes/MyBots.tsx | 38 ++++----- src/pages/settings/panes/Panes.module.scss | 1 + 5 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 src/context/intermediate/popovers/CreateBot.tsx diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index a2e5b1ec..05211969 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -16,6 +16,7 @@ import { Action } from "../../components/ui/Modal"; import { Children } from "../../types/Preact"; import Modals from "./Modals"; +import { Bot } from "revolt-api/types/Bots"; export type Screen = | { id: "none" } @@ -90,6 +91,7 @@ export type Screen = | { id: "channel_info"; channel: Channel } | { id: "pending_requests"; users: User[] } | { id: "modify_account"; field: "username" | "email" | "password" } + | { id: "create_bot"; onCreate: (bot: Bot) => void } | { id: "server_identity"; server: Server; diff --git a/src/context/intermediate/Popovers.tsx b/src/context/intermediate/Popovers.tsx index 0834a1f8..2d0d8996 100644 --- a/src/context/intermediate/Popovers.tsx +++ b/src/context/intermediate/Popovers.tsx @@ -8,6 +8,7 @@ import { IntermediateContext, useIntermediate } from "./Intermediate"; import { SpecialInputModal } from "./modals/Input"; import { SpecialPromptModal } from "./modals/Prompt"; import { ChannelInfo } from "./popovers/ChannelInfo"; +import { CreateBotModal } from "./popovers/CreateBot"; import { ImageViewer } from "./popovers/ImageViewer"; import { ModifyAccountModal } from "./popovers/ModifyAccount"; import { PendingRequests } from "./popovers/PendingRequests"; @@ -42,6 +43,9 @@ export default function Popovers() { case "modify_account": // @ts-expect-error someone figure this out :) return ; + case "create_bot": + // @ts-expect-error someone figure this out :) + return ; case "special_prompt": // @ts-expect-error someone figure this out :) return ; diff --git a/src/context/intermediate/popovers/CreateBot.tsx b/src/context/intermediate/popovers/CreateBot.tsx new file mode 100644 index 00000000..1f63940b --- /dev/null +++ b/src/context/intermediate/popovers/CreateBot.tsx @@ -0,0 +1,80 @@ +import { SubmitHandler, useForm } from "react-hook-form"; +import { Bot } from "revolt-api/types/Bots"; + +import { useContext, useState } from "preact/hooks"; + +import Modal from "../../../components/ui/Modal"; +import Overline from "../../../components/ui/Overline"; + +import FormField from "../../../pages/login/FormField"; +import { AppContext } from "../../revoltjs/RevoltClient"; +import { takeError } from "../../revoltjs/util"; + +interface Props { + onClose: () => void; + onCreate: (bot: Bot) => void; +} + +interface FormInputs { + name: string; +} + +export function CreateBotModal({ onClose, onCreate }: Props) { + const client = useContext(AppContext); + const { handleSubmit, register, errors } = useForm(); + const [error, setError] = useState(undefined); + + const onSubmit: SubmitHandler = async ({ name }) => { + try { + const { bot } = await client.bots.create({ name }); + onCreate(bot); + onClose(); + } catch (err) { + setError(takeError(err)); + } + }; + + return ( + + {/* Preact / React typing incompatabilities */} +
{ + e.preventDefault(); + handleSubmit( + onSubmit, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + )(e as any); + }}> + + {error && ( + + Failed to create a bot! + + )} + +
+ ); +} diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 22d5d125..0c8cc734 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -1,4 +1,4 @@ -import { Key, Clipboard, Globe } from "@styled-icons/boxicons-regular"; +import { Key, Clipboard, Globe, Plus } from "@styled-icons/boxicons-regular"; import { LockAlt } from "@styled-icons/boxicons-solid"; import { observer } from "mobx-react-lite"; import { Bot } from "revolt-api/types/Bots"; @@ -21,7 +21,6 @@ import UserIcon from "../../../components/common/user/UserIcon"; import Button from "../../../components/ui/Button"; import Checkbox from "../../../components/ui/Checkbox"; import InputBox from "../../../components/ui/InputBox"; -import Overline from "../../../components/ui/Overline"; import Tip from "../../../components/ui/Tip"; import CategoryButton from "../../../components/ui/fluent/CategoryButton"; @@ -349,34 +348,25 @@ export const MyBots = observer(() => { // eslint-disable-next-line }, []); - const [name, setName] = useState(""); + const { openScreen } = useIntermediate(); return (
This section is under construction. - create a new bot -

- setName(e.currentTarget.value)} - /> -

-

- -

- my bots + } + onClick={() => + openScreen({ + id: "create_bot", + onCreate: (bot) => setBots([...(bots ?? []), bot]), + }) + } + action="chevron"> + Create a Bot + {bots?.map((bot) => { return ( Date: Tue, 31 Aug 2021 04:33:47 +0000 Subject: [PATCH 07/11] update create bot form --- src/context/intermediate/popovers/CreateBot.tsx | 5 +++-- src/pages/login/FormField.tsx | 11 ++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/context/intermediate/popovers/CreateBot.tsx b/src/context/intermediate/popovers/CreateBot.tsx index 1f63940b..6010ba4f 100644 --- a/src/context/intermediate/popovers/CreateBot.tsx +++ b/src/context/intermediate/popovers/CreateBot.tsx @@ -1,6 +1,7 @@ import { SubmitHandler, useForm } from "react-hook-form"; import { Bot } from "revolt-api/types/Bots"; +import { Text } from "preact-i18n"; import { useContext, useState } from "preact/hooks"; import Modal from "../../../components/ui/Modal"; @@ -45,12 +46,12 @@ export function CreateBotModal({ onClose, onCreate }: Props) { contrast: true, accent: true, onClick: handleSubmit(onSubmit), - children: "Create", + children: , }, { plain: true, onClick: onClose, - children: "Cancel", + children: , }, ]}> {/* Preact / React typing incompatabilities */} diff --git a/src/pages/login/FormField.tsx b/src/pages/login/FormField.tsx index 719346c7..9f8b9131 100644 --- a/src/pages/login/FormField.tsx +++ b/src/pages/login/FormField.tsx @@ -67,7 +67,16 @@ export default function FormField({ }, } : type === "username" - ? { required: "RequiredField" } + ? { + validate: (value: string) => + value.length === 0 + ? "RequiredField" + : value.length < 2 + ? "TooShort" + : value.length > 32 + ? "TooLong" + : undefined, + } : { required: "RequiredField" }, )} /> From 6e031d7326f9c87bb096b8ab0ecb4679e8d5e84b Mon Sep 17 00:00:00 2001 From: Snazzah <7025343+Snazzah@users.noreply.github.com> Date: Tue, 31 Aug 2021 04:50:48 +0000 Subject: [PATCH 08/11] update strings and style --- src/pages/settings/panes/MyBots.tsx | 6 +++--- src/pages/settings/panes/Panes.module.scss | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 0c8cc734..c837fa7d 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -230,7 +230,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { } else setEditMode(true); }} contrast> - {editMode ? "Cancel" : "Edit"} + {editMode ? : "Edit"}
{!editMode && ( @@ -298,7 +298,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { {editMode && ( <> )} diff --git a/src/pages/settings/panes/Panes.module.scss b/src/pages/settings/panes/Panes.module.scss index de65bde3..545e9997 100644 --- a/src/pages/settings/panes/Panes.module.scss +++ b/src/pages/settings/panes/Panes.module.scss @@ -532,6 +532,7 @@ border-radius: var(--border-radius); h5 { margin-bottom: 1em } + h3 { margin-bottom: 0 } } .botSection { From c3c44ab92d6ab7f98f84092bec9185da31e7f237 Mon Sep 17 00:00:00 2001 From: Snazzah <7025343+Snazzah@users.noreply.github.com> Date: Tue, 31 Aug 2021 04:51:13 +0000 Subject: [PATCH 09/11] remove under construction tip --- src/pages/settings/panes/MyBots.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index c837fa7d..3db0ae73 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -352,9 +352,6 @@ export const MyBots = observer(() => { return (
- - This section is under construction. - } From cb708cc77e0e5dc431ffcea2e93faf915b46deab Mon Sep 17 00:00:00 2001 From: Snazzah <7025343+Snazzah@users.noreply.github.com> Date: Tue, 31 Aug 2021 15:37:34 +0000 Subject: [PATCH 10/11] fix typing errors --- src/pages/settings/panes/MyBots.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index 3db0ae73..dc05fca7 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -23,6 +23,7 @@ import Checkbox from "../../../components/ui/Checkbox"; import InputBox from "../../../components/ui/InputBox"; import Tip from "../../../components/ui/Tip"; import CategoryButton from "../../../components/ui/fluent/CategoryButton"; +import type { AxiosError } from "axios"; interface Data { _id: string; @@ -92,16 +93,17 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { onUpdate(changes); setEditMode(false); } catch (e) { - if (e.isAxiosError && e.response.data?.type) { - switch (e.response.data.type) { + const err = e as AxiosError; + if (err.isAxiosError && err.response?.data?.type) { + switch (err.response.data.type) { case "UsernameTaken": setError("That username is taken!"); break; default: - setError(`Error: ${e.response.data.type}`); + setError(`Error: ${err.response.data.type}`); break; } - } else setError(e.toString()); + } else setError(err.toString()); } setSaving(false); } From 33cc325099de86ccd771b8072038e4ddec73cc14 Mon Sep 17 00:00:00 2001 From: Snazzah <7025343+Snazzah@users.noreply.github.com> Date: Tue, 31 Aug 2021 15:55:18 +0000 Subject: [PATCH 11/11] finish translations --- .../intermediate/modals/TokenReveal.tsx | 9 ++++++-- .../intermediate/popovers/CreateBot.tsx | 4 ++-- src/pages/settings/panes/MyBots.tsx | 23 ++++++++----------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/context/intermediate/modals/TokenReveal.tsx b/src/context/intermediate/modals/TokenReveal.tsx index 5fa8a288..a0d8edfd 100644 --- a/src/context/intermediate/modals/TokenReveal.tsx +++ b/src/context/intermediate/modals/TokenReveal.tsx @@ -8,12 +8,17 @@ interface Props { username: string; } -export function TokenRevealModal({ onClose, token,username }: Props) { +export function TokenRevealModal({ onClose, token, username }: Props) { return ( + } actions={[ { onClick: onClose, diff --git a/src/context/intermediate/popovers/CreateBot.tsx b/src/context/intermediate/popovers/CreateBot.tsx index 6010ba4f..48000382 100644 --- a/src/context/intermediate/popovers/CreateBot.tsx +++ b/src/context/intermediate/popovers/CreateBot.tsx @@ -39,7 +39,7 @@ export function CreateBotModal({ onClose, onCreate }: Props) { } actions={[ { confirmation: true, @@ -72,7 +72,7 @@ export function CreateBotModal({ onClose, onCreate }: Props) { /> {error && ( - Failed to create a bot! + )} diff --git a/src/pages/settings/panes/MyBots.tsx b/src/pages/settings/panes/MyBots.tsx index dc05fca7..09b05e1f 100644 --- a/src/pages/settings/panes/MyBots.tsx +++ b/src/pages/settings/panes/MyBots.tsx @@ -203,10 +203,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { {!editMode && ( }> {bot.public ? ( @@ -232,7 +229,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { } else setEditMode(true); }} contrast> - {editMode ? : "Edit"} +
{!editMode && ( @@ -259,7 +256,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { } action={}> - Token + )} {editMode && ( @@ -268,12 +265,12 @@ function BotCard({ bot, onDelete, onUpdate }: Props) { checked={data.public} disabled={saving} contrast - description="Whether to allow other users to invite this bot." + description={} onChange={(v) => setData({ ...data, public: v })}> - Public Bot + -

Interactions URL

-
This field is reserved for the future.
+

+
- Copy Invite Link + )} @@ -364,7 +361,7 @@ export const MyBots = observer(() => { }) } action="chevron"> - Create a Bot + {bots?.map((bot) => { return (