revite/src/pages/settings/panes/MyBots.tsx

401 lines
15 KiB
TypeScript
Raw Normal View History

2021-08-31 00:00:45 -04:00
import { Key, Clipboard, Globe, Plus } from "@styled-icons/boxicons-regular";
2021-08-30 12:18:42 -04:00
import { LockAlt } from "@styled-icons/boxicons-solid";
2021-08-12 10:29:19 -04:00
import { observer } from "mobx-react-lite";
import { Bot } from "revolt-api/types/Bots";
2021-08-30 22:33:36 -04:00
import { User } from "revolt.js/dist/maps/Users";
2021-08-30 12:18:42 -04:00
import styled from "styled-components";
2021-08-12 10:29:19 -04:00
2021-08-30 12:18:42 -04:00
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
2021-08-12 10:29:19 -04:00
import { useEffect, useState } from "preact/hooks";
2021-08-30 22:33:36 -04:00
import { internalEmit } from "../../../lib/eventEmitter";
2021-08-30 23:23:07 -04:00
import { stopPropagation } from "../../../lib/stopPropagation";
2021-08-30 12:18:42 -04:00
2021-08-12 11:07:41 -04:00
import { useIntermediate } from "../../../context/intermediate/Intermediate";
2021-08-30 21:42:50 -04:00
import { FileUploader } from "../../../context/revoltjs/FileUploads";
2021-08-12 10:29:19 -04:00
import { useClient } from "../../../context/revoltjs/RevoltClient";
2021-08-30 12:18:42 -04:00
import Tooltip from "../../../components/common/Tooltip";
import UserIcon from "../../../components/common/user/UserIcon";
2021-08-12 10:29:19 -04:00
import Button from "../../../components/ui/Button";
import Checkbox from "../../../components/ui/Checkbox";
import InputBox from "../../../components/ui/InputBox";
import Tip from "../../../components/ui/Tip";
2021-08-30 12:18:42 -04:00
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
2021-08-31 11:37:34 -04:00
import type { AxiosError } from "axios";
2021-08-12 10:29:19 -04:00
interface Data {
_id: string;
username: string;
public: boolean;
interactions_url?: string;
}
2021-08-30 15:10:55 -04:00
interface Changes {
name?: string;
public?: boolean;
interactions_url?: string;
remove?: "InteractionsURL";
}
2021-08-30 12:18:42 -04:00
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);
`;
2021-08-30 15:10:55 -04:00
interface Props {
bot: Bot;
2021-08-30 21:42:50 -04:00
onDelete(): void;
onUpdate(changes: Changes): void;
2021-08-30 15:10:55 -04:00
}
2021-08-30 21:42:50 -04:00
function BotCard({ bot, onDelete, onUpdate }: Props) {
const client = useClient();
const [user, setUser] = useState<User>(client.users.get(bot._id)!);
2021-08-30 15:10:55 -04:00
const [data, setData] = useState<Data>({
_id: bot._id,
2021-08-30 21:42:50 -04:00
username: user.username,
2021-08-30 15:10:55 -04:00
public: bot.public,
interactions_url: bot.interactions_url,
});
2021-08-30 23:23:07 -04:00
const [error, setError] = useState<string | JSX.Element>("");
2021-08-30 15:10:55 -04:00
const [saving, setSaving] = useState(false);
const [editMode, setEditMode] = useState(false);
const [usernameRef, setUsernameRef] = useState<HTMLInputElement | null>(
null,
);
const [interactionsRef, setInteractionsRef] =
useState<HTMLInputElement | null>(null);
const { writeClipboard, openScreen } = useIntermediate();
2021-08-12 10:29:19 -04:00
2021-08-30 15:10:55 -04:00
async function save() {
const changes: Changes = {};
if (data.username !== user!.username) changes.name = data.username;
2021-08-12 10:29:19 -04:00
if (data.public !== bot.public) changes.public = data.public;
2021-08-30 21:42:50 -04:00
if (data.interactions_url === "") changes.remove = "InteractionsURL";
2021-08-30 15:10:55 -04:00
else if (data.interactions_url !== bot.interactions_url)
2021-08-12 10:29:19 -04:00
changes.interactions_url = data.interactions_url;
2021-08-30 15:10:55 -04:00
setSaving(true);
2021-08-30 23:23:07 -04:00
setError("");
2021-08-30 15:10:55 -04:00
try {
2021-08-30 21:42:50 -04:00
await client.bots.edit(bot._id, changes);
onUpdate(changes);
2021-08-30 15:10:55 -04:00
setEditMode(false);
} catch (e) {
2021-08-31 11:37:34 -04:00
const err = e as AxiosError;
if (err.isAxiosError && err.response?.data?.type) {
switch (err.response.data.type) {
2021-08-30 23:23:07 -04:00
case "UsernameTaken":
setError("That username is taken!");
break;
default:
2021-08-31 11:37:34 -04:00
setError(`Error: ${err.response.data.type}`);
2021-08-30 23:23:07 -04:00
break;
}
2021-08-31 11:37:34 -04:00
} else setError(err.toString());
2021-08-30 15:10:55 -04:00
}
setSaving(false);
2021-08-12 10:29:19 -04:00
}
2021-08-30 21:42:50 -04:00
async function editBotAvatar(avatar?: string) {
setSaving(true);
2021-08-30 23:23:07 -04:00
setError("");
2021-08-30 21:42:50 -04:00
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);
}
2021-08-12 10:29:19 -04:00
return (
2021-08-30 23:23:07 -04:00
<div key={bot._id} className={styles.botCard}>
2021-08-30 15:10:55 -04:00
<div className={styles.infoheader}>
<div className={styles.container}>
2021-08-30 21:42:50 -04:00
{!editMode ? (
<UserIcon
className={styles.avatar}
target={user}
size={48}
onClick={() =>
openScreen({
id: "profile",
user_id: user._id,
})
}
/>
) : (
<FileUploader
width={64}
height={64}
style="icon"
fileType="avatars"
behaviour="upload"
maxFileSize={4_000_000}
onUpload={(avatar) => editBotAvatar(avatar)}
remove={() => editBotAvatar()}
defaultPreview={user.generateAvatarURL(
{ max_side: 256 },
true,
)}
previewURL={user.generateAvatarURL(
{ max_side: 256 },
true,
)}
/>
)}
2021-08-30 15:10:55 -04:00
{!editMode ? (
<div className={styles.userDetail}>
<div className={styles.userName}>
{user!.username}{" "}
<BotBadge>
<Text id="app.main.channel.bot" />
</BotBadge>
</div>
<div className={styles.userid}>
<Tooltip
content={<Text id="app.special.copy" />}>
<a
onClick={() =>
writeClipboard(user!._id)
}>
{user!._id}
</a>
</Tooltip>
</div>
</div>
) : (
<InputBox
ref={setUsernameRef}
value={data.username}
disabled={saving}
onChange={(e) =>
setData({
...data,
username: e.currentTarget.value,
})
}
/>
)}
</div>
{!editMode && (
<Tooltip
2021-08-31 11:55:18 -04:00
content={<Text id={`app.settings.pages.bots.${bot.public ? 'public' : 'private'}_bot_tip`} />
2021-08-30 15:10:55 -04:00
}>
{bot.public ? (
<Globe size={24} />
) : (
<LockAlt size={24} />
)}
</Tooltip>
)}
<Button
2021-08-30 21:42:50 -04:00
disabled={saving}
2021-08-30 15:10:55 -04:00
onClick={() => {
if (editMode) {
setData({
_id: bot._id,
username: user!.username,
public: bot.public,
interactions_url: bot.interactions_url,
});
usernameRef!.value = user!.username;
interactionsRef!.value = bot.interactions_url || "";
2021-08-30 23:23:07 -04:00
setError("");
2021-08-30 15:10:55 -04:00
setEditMode(false);
} else setEditMode(true);
}}
contrast>
2021-08-31 11:55:18 -04:00
<Text id={`app.special.modals.actions.${editMode ? 'cancel' : 'edit'}`} />
2021-08-30 15:10:55 -04:00
</Button>
</div>
{!editMode && (
<CategoryButton
account
icon={<Key size={24} />}
onClick={() => writeClipboard(bot.token)}
description={
<>
{"••••••••••••••••••••••••••••••••••••"}{" "}
<a
onClick={(ev) =>
stopPropagation(
ev,
openScreen({
id: "token_reveal",
token: bot.token,
username: user!.username,
}),
)
}>
<Text id="app.special.modals.actions.reveal" />
</a>
</>
2021-08-12 10:29:19 -04:00
}
2021-08-30 15:10:55 -04:00
action={<Clipboard size={18} />}>
2021-08-31 11:55:18 -04:00
<Text id="app.settings.pages.bots.token" />
2021-08-30 15:10:55 -04:00
</CategoryButton>
)}
{editMode && (
2021-08-30 23:23:07 -04:00
<div className={styles.botSection}>
2021-08-30 15:10:55 -04:00
<Checkbox
checked={data.public}
disabled={saving}
contrast
2021-08-31 11:55:18 -04:00
description={<Text id="app.settings.pages.bots.public_bot_desc" />}
2021-08-30 15:10:55 -04:00
onChange={(v) => setData({ ...data, public: v })}>
2021-08-31 11:55:18 -04:00
<Text id="app.settings.pages.bots.public_bot" />
2021-08-30 15:10:55 -04:00
</Checkbox>
2021-08-31 11:55:18 -04:00
<h3><Text id="app.settings.pages.bots.interactions_url" /></h3>
<h5><Text id="app.settings.pages.bots.reserved" /></h5>
2021-08-30 15:10:55 -04:00
<InputBox
ref={setInteractionsRef}
value={data.interactions_url}
disabled={saving}
onChange={(e) =>
setData({
...data,
2021-08-30 21:42:50 -04:00
interactions_url: e.currentTarget.value,
2021-08-30 15:10:55 -04:00
})
}
/>
2021-08-30 23:23:07 -04:00
</div>
)}
{error && (
<div className={styles.botSection}>
<Tip error hideSeparator>
{error}
</Tip>
</div>
2021-08-30 15:10:55 -04:00
)}
<div className={styles.buttonRow}>
{editMode && (
<>
<Button accent onClick={save}>
2021-08-31 00:50:48 -04:00
<Text id="app.special.modals.actions.save" />
2021-08-30 15:10:55 -04:00
</Button>
<Button
error
2021-08-30 21:42:50 -04:00
onClick={async () => {
setSaving(true);
await client.bots.delete(bot._id);
onDelete();
}}>
2021-08-31 00:50:48 -04:00
<Text id="app.special.modals.actions.delete" />
2021-08-30 15:10:55 -04:00
</Button>
</>
)}
{!editMode && (
2021-08-30 22:33:36 -04:00
<>
<Button
onClick={() =>
writeClipboard(
`${window.origin}/bot/${bot._id}`,
)
}>
2021-08-31 11:55:18 -04:00
<Text id="app.settings.pages.bots.copy_invite" />
2021-08-30 22:33:36 -04:00
</Button>
<Button
accent
onClick={() =>
internalEmit(
"Intermediate",
"navigate",
`/bot/${bot._id}`,
)
}>
2021-08-31 11:55:18 -04:00
<Text id="app.settings.pages.bots.add_bot" />
2021-08-30 22:33:36 -04:00
</Button>
</>
2021-08-30 15:10:55 -04:00
)}
</div>
2021-08-12 10:29:19 -04:00
</div>
);
}
export const MyBots = observer(() => {
const client = useClient();
const [bots, setBots] = useState<Bot[] | undefined>(undefined);
useEffect(() => {
client.bots.fetchOwned().then(({ bots }) => setBots(bots));
// eslint-disable-next-line
}, []);
2021-08-31 00:00:45 -04:00
const { openScreen } = useIntermediate();
2021-08-12 10:29:19 -04:00
return (
2021-08-30 12:18:42 -04:00
<div className={styles.myBots}>
2021-08-31 00:00:45 -04:00
<CategoryButton
account
icon={<Plus size={24} />}
onClick={() =>
openScreen({
id: "create_bot",
onCreate: (bot) => setBots([...(bots ?? []), bot]),
})
}
action="chevron">
2021-08-31 11:55:18 -04:00
<Text id="app.settings.pages.bots.create_bot" />
2021-08-31 00:00:45 -04:00
</CategoryButton>
2021-08-12 10:29:19 -04:00
{bots?.map((bot) => {
return (
2021-08-30 15:10:55 -04:00
<BotCard
2021-08-12 10:29:19 -04:00
key={bot._id}
2021-08-30 15:10:55 -04:00
bot={bot}
onDelete={() =>
2021-08-30 21:42:50 -04:00
setBots(bots.filter((x) => x._id !== bot._id))
2021-08-30 15:10:55 -04:00
}
onUpdate={(changes: Changes) =>
2021-08-30 21:42:50 -04:00
setBots(
2021-08-30 15:10:55 -04:00
bots.map((x) => {
if (x._id === bot._id) {
2021-08-30 21:42:50 -04:00
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;
2021-08-30 15:10:55 -04:00
}
return x;
}),
2021-08-30 21:42:50 -04:00
)
2021-08-30 15:10:55 -04:00
}
/>
2021-08-12 10:29:19 -04:00
);
})}
</div>
);
});