mirror of
https://github.com/revoltchat/revite.git
synced 2024-12-26 07:22:10 -05:00
Merge pull request #159 from Snazzah/bots-look-cool
This commit is contained in:
commit
8ad1130a00
10 changed files with 585 additions and 113 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true,
|
||||||
|
"compile-hero.disable-compile-files-on-did-save-code": true
|
||||||
}
|
}
|
|
@ -51,7 +51,7 @@ const CheckboxDescription = styled.span`
|
||||||
color: var(--secondary-foreground);
|
color: var(--secondary-foreground);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Checkmark = styled.div<{ checked: boolean }>`
|
const Checkmark = styled.div<{ checked: boolean; contrast?: boolean }>`
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
@ -66,6 +66,16 @@ const Checkmark = styled.div<{ checked: boolean }>`
|
||||||
color: var(--secondary-background);
|
color: var(--secondary-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.contrast &&
|
||||||
|
css`
|
||||||
|
background: var(--primary-background);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: var(--primary-background);
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.checked &&
|
props.checked &&
|
||||||
css`
|
css`
|
||||||
|
@ -76,6 +86,7 @@ const Checkmark = styled.div<{ checked: boolean }>`
|
||||||
export interface CheckboxProps {
|
export interface CheckboxProps {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
contrast?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
children: Children;
|
children: Children;
|
||||||
description?: Children;
|
description?: Children;
|
||||||
|
@ -100,7 +111,7 @@ export default function Checkbox(props: CheckboxProps) {
|
||||||
!props.disabled && props.onChange(!props.checked)
|
!props.disabled && props.onChange(!props.checked)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Checkmark checked={props.checked} className="check">
|
<Checkmark checked={props.checked} contrast={props.contrast} className="check">
|
||||||
<Check size={20} />
|
<Check size={20} />
|
||||||
</Checkmark>
|
</Checkmark>
|
||||||
</CheckboxBase>
|
</CheckboxBase>
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { Action } from "../../components/ui/Modal";
|
||||||
|
|
||||||
import { Children } from "../../types/Preact";
|
import { Children } from "../../types/Preact";
|
||||||
import Modals from "./Modals";
|
import Modals from "./Modals";
|
||||||
|
import { Bot } from "revolt-api/types/Bots";
|
||||||
|
|
||||||
export type Screen =
|
export type Screen =
|
||||||
| { id: "none" }
|
| { id: "none" }
|
||||||
|
@ -24,6 +25,7 @@ export type Screen =
|
||||||
| { id: "signed_out" }
|
| { id: "signed_out" }
|
||||||
| { id: "error"; error: string }
|
| { id: "error"; error: string }
|
||||||
| { id: "clipboard"; text: string }
|
| { id: "clipboard"; text: string }
|
||||||
|
| { id: "token_reveal"; token: string; username: string }
|
||||||
| { id: "external_link_prompt"; link: string }
|
| { id: "external_link_prompt"; link: string }
|
||||||
| {
|
| {
|
||||||
id: "_prompt";
|
id: "_prompt";
|
||||||
|
@ -89,6 +91,7 @@ export type Screen =
|
||||||
| { id: "channel_info"; channel: Channel }
|
| { id: "channel_info"; channel: Channel }
|
||||||
| { id: "pending_requests"; users: User[] }
|
| { id: "pending_requests"; users: User[] }
|
||||||
| { id: "modify_account"; field: "username" | "email" | "password" }
|
| { id: "modify_account"; field: "username" | "email" | "password" }
|
||||||
|
| { id: "create_bot"; onCreate: (bot: Bot) => void }
|
||||||
| {
|
| {
|
||||||
id: "server_identity";
|
id: "server_identity";
|
||||||
server: Server;
|
server: Server;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { OnboardingModal } from "./modals/Onboarding";
|
||||||
import { PromptModal } from "./modals/Prompt";
|
import { PromptModal } from "./modals/Prompt";
|
||||||
import { SignedOutModal } from "./modals/SignedOut";
|
import { SignedOutModal } from "./modals/SignedOut";
|
||||||
import {ExternalLinkModal} from "./modals/ExternalLinkPrompt";
|
import {ExternalLinkModal} from "./modals/ExternalLinkPrompt";
|
||||||
|
import { TokenRevealModal } from "./modals/TokenReveal";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
screen: Screen;
|
screen: Screen;
|
||||||
|
@ -33,6 +34,8 @@ export default function Modals({ screen, openScreen }: Props) {
|
||||||
return <SignedOutModal onClose={onClose} {...screen} />;
|
return <SignedOutModal onClose={onClose} {...screen} />;
|
||||||
case "clipboard":
|
case "clipboard":
|
||||||
return <ClipboardModal onClose={onClose} {...screen} />;
|
return <ClipboardModal onClose={onClose} {...screen} />;
|
||||||
|
case "token_reveal":
|
||||||
|
return <TokenRevealModal onClose={onClose} {...screen} />;
|
||||||
case "onboarding":
|
case "onboarding":
|
||||||
return <OnboardingModal onClose={onClose} {...screen} />;
|
return <OnboardingModal onClose={onClose} {...screen} />;
|
||||||
case "external_link_prompt":
|
case "external_link_prompt":
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { IntermediateContext, useIntermediate } from "./Intermediate";
|
||||||
import { SpecialInputModal } from "./modals/Input";
|
import { SpecialInputModal } from "./modals/Input";
|
||||||
import { SpecialPromptModal } from "./modals/Prompt";
|
import { SpecialPromptModal } from "./modals/Prompt";
|
||||||
import { ChannelInfo } from "./popovers/ChannelInfo";
|
import { ChannelInfo } from "./popovers/ChannelInfo";
|
||||||
|
import { CreateBotModal } from "./popovers/CreateBot";
|
||||||
import { ImageViewer } from "./popovers/ImageViewer";
|
import { ImageViewer } from "./popovers/ImageViewer";
|
||||||
import { ModifyAccountModal } from "./popovers/ModifyAccount";
|
import { ModifyAccountModal } from "./popovers/ModifyAccount";
|
||||||
import { PendingRequests } from "./popovers/PendingRequests";
|
import { PendingRequests } from "./popovers/PendingRequests";
|
||||||
|
@ -42,6 +43,9 @@ export default function Popovers() {
|
||||||
case "modify_account":
|
case "modify_account":
|
||||||
// @ts-expect-error someone figure this out :)
|
// @ts-expect-error someone figure this out :)
|
||||||
return <ModifyAccountModal onClose={onClose} {...screen} />;
|
return <ModifyAccountModal onClose={onClose} {...screen} />;
|
||||||
|
case "create_bot":
|
||||||
|
// @ts-expect-error someone figure this out :)
|
||||||
|
return <CreateBotModal onClose={onClose} {...screen} />;
|
||||||
case "special_prompt":
|
case "special_prompt":
|
||||||
// @ts-expect-error someone figure this out :)
|
// @ts-expect-error someone figure this out :)
|
||||||
return <SpecialPromptModal onClose={onClose} {...screen} />;
|
return <SpecialPromptModal onClose={onClose} {...screen} />;
|
||||||
|
|
32
src/context/intermediate/modals/TokenReveal.tsx
Normal file
32
src/context/intermediate/modals/TokenReveal.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
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 (
|
||||||
|
<Modal
|
||||||
|
visible={true}
|
||||||
|
onClose={onClose}
|
||||||
|
title={
|
||||||
|
<Text
|
||||||
|
id={"app.special.modals.token_reveal"}
|
||||||
|
fields={{ name: username }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
onClick: onClose,
|
||||||
|
confirmation: true,
|
||||||
|
children: <Text id="app.special.modals.actions.close" />,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<code style={{ userSelect: "all" }}>{token}</code>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
81
src/context/intermediate/popovers/CreateBot.tsx
Normal file
81
src/context/intermediate/popovers/CreateBot.tsx
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
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";
|
||||||
|
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<FormInputs>();
|
||||||
|
const [error, setError] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<FormInputs> = async ({ name }) => {
|
||||||
|
try {
|
||||||
|
const { bot } = await client.bots.create({ name });
|
||||||
|
onCreate(bot);
|
||||||
|
onClose();
|
||||||
|
} catch (err) {
|
||||||
|
setError(takeError(err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={true}
|
||||||
|
onClose={onClose}
|
||||||
|
title={<Text id="app.special.popovers.create_bot.title" />}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
confirmation: true,
|
||||||
|
contrast: true,
|
||||||
|
accent: true,
|
||||||
|
onClick: handleSubmit(onSubmit),
|
||||||
|
children: <Text id="app.special.modals.actions.create" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
plain: true,
|
||||||
|
onClick: onClose,
|
||||||
|
children: <Text id="app.special.modals.actions.cancel" />,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{/* Preact / React typing incompatabilities */}
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSubmit(
|
||||||
|
onSubmit,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
)(e as any);
|
||||||
|
}}>
|
||||||
|
<FormField
|
||||||
|
type="username"
|
||||||
|
name="name"
|
||||||
|
register={register}
|
||||||
|
showOverline
|
||||||
|
error={errors.name?.message}
|
||||||
|
/>
|
||||||
|
{error && (
|
||||||
|
<Overline type="error" error={error}>
|
||||||
|
<Text id="app.special.popovers.create_bot.error" />
|
||||||
|
</Overline>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
|
@ -67,7 +67,16 @@ export default function FormField({
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: type === "username"
|
: type === "username"
|
||||||
? { required: "RequiredField" }
|
? {
|
||||||
|
validate: (value: string) =>
|
||||||
|
value.length === 0
|
||||||
|
? "RequiredField"
|
||||||
|
: value.length < 2
|
||||||
|
? "TooShort"
|
||||||
|
: value.length > 32
|
||||||
|
? "TooLong"
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
: { required: "RequiredField" },
|
: { required: "RequiredField" },
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,17 +1,29 @@
|
||||||
|
import { Key, Clipboard, Globe, Plus } from "@styled-icons/boxicons-regular";
|
||||||
|
import { LockAlt } from "@styled-icons/boxicons-solid";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Bot } from "revolt-api/types/Bots";
|
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";
|
||||||
|
import { Text } from "preact-i18n";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import { internalEmit } from "../../../lib/eventEmitter";
|
||||||
|
import { stopPropagation } from "../../../lib/stopPropagation";
|
||||||
|
|
||||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||||
|
import { FileUploader } from "../../../context/revoltjs/FileUploads";
|
||||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
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 Button from "../../../components/ui/Button";
|
||||||
import Checkbox from "../../../components/ui/Checkbox";
|
import Checkbox from "../../../components/ui/Checkbox";
|
||||||
import InputBox from "../../../components/ui/InputBox";
|
import InputBox from "../../../components/ui/InputBox";
|
||||||
import Overline from "../../../components/ui/Overline";
|
|
||||||
import Tip from "../../../components/ui/Tip";
|
import Tip from "../../../components/ui/Tip";
|
||||||
|
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
|
||||||
|
import type { AxiosError } from "axios";
|
||||||
|
|
||||||
interface Data {
|
interface Data {
|
||||||
_id: string;
|
_id: string;
|
||||||
|
@ -20,41 +32,249 @@ interface Data {
|
||||||
interactions_url?: string;
|
interactions_url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BotEditor({ bot }: { bot: Data }) {
|
interface Changes {
|
||||||
|
name?: string;
|
||||||
|
public?: boolean;
|
||||||
|
interactions_url?: string;
|
||||||
|
remove?: "InteractionsURL";
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
bot: Bot;
|
||||||
|
onDelete(): void;
|
||||||
|
onUpdate(changes: Changes): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const [data, setData] = useState<Data>(bot);
|
const [user, setUser] = useState<User>(client.users.get(bot._id)!);
|
||||||
|
const [data, setData] = useState<Data>({
|
||||||
|
_id: bot._id,
|
||||||
|
username: user.username,
|
||||||
|
public: bot.public,
|
||||||
|
interactions_url: bot.interactions_url,
|
||||||
|
});
|
||||||
|
const [error, setError] = useState<string | JSX.Element>("");
|
||||||
|
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();
|
||||||
|
|
||||||
function save() {
|
async function save() {
|
||||||
const changes: Record<string, string | boolean | undefined> = {};
|
const changes: Changes = {};
|
||||||
if (data.username !== bot.username) changes.name = data.username;
|
if (data.username !== user!.username) changes.name = data.username;
|
||||||
if (data.public !== bot.public) changes.public = data.public;
|
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;
|
changes.interactions_url = data.interactions_url;
|
||||||
|
setSaving(true);
|
||||||
|
setError("");
|
||||||
|
try {
|
||||||
|
await client.bots.edit(bot._id, changes);
|
||||||
|
onUpdate(changes);
|
||||||
|
setEditMode(false);
|
||||||
|
} catch (e) {
|
||||||
|
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: ${err.response.data.type}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else setError(err.toString());
|
||||||
|
}
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
|
||||||
client.bots.edit(bot._id, changes);
|
async function editBotAvatar(avatar?: string) {
|
||||||
|
setSaving(true);
|
||||||
|
setError("");
|
||||||
|
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 (
|
return (
|
||||||
<div>
|
<div key={bot._id} className={styles.botCard}>
|
||||||
<p>
|
<div className={styles.infoheader}>
|
||||||
<InputBox
|
<div className={styles.container}>
|
||||||
value={data.username}
|
{!editMode ? (
|
||||||
onChange={(e) =>
|
<UserIcon
|
||||||
setData({ ...data, username: e.currentTarget.value })
|
className={styles.avatar}
|
||||||
|
target={user}
|
||||||
|
size={48}
|
||||||
|
onClick={() =>
|
||||||
|
openScreen({
|
||||||
|
id: "profile",
|
||||||
|
user_id: user._id,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</p>
|
) : (
|
||||||
<p>
|
<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,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!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
|
||||||
|
content={<Text id={`app.settings.pages.bots.${bot.public ? 'public' : 'private'}_bot_tip`} />
|
||||||
|
}>
|
||||||
|
{bot.public ? (
|
||||||
|
<Globe size={24} />
|
||||||
|
) : (
|
||||||
|
<LockAlt size={24} />
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
disabled={saving}
|
||||||
|
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 || "";
|
||||||
|
setError("");
|
||||||
|
setEditMode(false);
|
||||||
|
} else setEditMode(true);
|
||||||
|
}}
|
||||||
|
contrast>
|
||||||
|
<Text id={`app.special.modals.actions.${editMode ? 'cancel' : 'edit'}`} />
|
||||||
|
</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>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
action={<Clipboard size={18} />}>
|
||||||
|
<Text id="app.settings.pages.bots.token" />
|
||||||
|
</CategoryButton>
|
||||||
|
)}
|
||||||
|
{editMode && (
|
||||||
|
<div className={styles.botSection}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={data.public}
|
checked={data.public}
|
||||||
|
disabled={saving}
|
||||||
|
contrast
|
||||||
|
description={<Text id="app.settings.pages.bots.public_bot_desc" />}
|
||||||
onChange={(v) => setData({ ...data, public: v })}>
|
onChange={(v) => setData({ ...data, public: v })}>
|
||||||
is public
|
<Text id="app.settings.pages.bots.public_bot" />
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</p>
|
<h3><Text id="app.settings.pages.bots.interactions_url" /></h3>
|
||||||
<p>interactions url: (reserved for the future)</p>
|
<h5><Text id="app.settings.pages.bots.reserved" /></h5>
|
||||||
<p>
|
|
||||||
<InputBox
|
<InputBox
|
||||||
|
ref={setInteractionsRef}
|
||||||
value={data.interactions_url}
|
value={data.interactions_url}
|
||||||
|
disabled={saving}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setData({
|
setData({
|
||||||
...data,
|
...data,
|
||||||
|
@ -62,8 +282,58 @@ function BotEditor({ bot }: { bot: Data }) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</p>
|
</div>
|
||||||
<Button onClick={save}>save</Button>
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className={styles.botSection}>
|
||||||
|
<Tip error hideSeparator>
|
||||||
|
{error}
|
||||||
|
</Tip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.buttonRow}>
|
||||||
|
{editMode && (
|
||||||
|
<>
|
||||||
|
<Button accent onClick={save}>
|
||||||
|
<Text id="app.special.modals.actions.save" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
error
|
||||||
|
onClick={async () => {
|
||||||
|
setSaving(true);
|
||||||
|
await client.bots.delete(bot._id);
|
||||||
|
onDelete();
|
||||||
|
}}>
|
||||||
|
<Text id="app.special.modals.actions.delete" />
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!editMode && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
writeClipboard(
|
||||||
|
`${window.origin}/bot/${bot._id}`,
|
||||||
|
)
|
||||||
|
}>
|
||||||
|
<Text id="app.settings.pages.bots.copy_invite" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
accent
|
||||||
|
onClick={() =>
|
||||||
|
internalEmit(
|
||||||
|
"Intermediate",
|
||||||
|
"navigate",
|
||||||
|
`/bot/${bot._id}`,
|
||||||
|
)
|
||||||
|
}>
|
||||||
|
<Text id="app.settings.pages.bots.add_bot" />
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -77,82 +347,52 @@ export const MyBots = observer(() => {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [name, setName] = useState("");
|
const { openScreen } = useIntermediate();
|
||||||
const { writeClipboard } = useIntermediate();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={styles.myBots}>
|
||||||
<Tip warning hideSeparator>
|
<CategoryButton
|
||||||
This section is under construction.
|
account
|
||||||
</Tip>
|
icon={<Plus size={24} />}
|
||||||
<Overline>create a new bot</Overline>
|
|
||||||
<p>
|
|
||||||
<InputBox
|
|
||||||
value={name}
|
|
||||||
contrast
|
|
||||||
onChange={(e) => setName(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<Button
|
|
||||||
contrast
|
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
name.length > 0 &&
|
openScreen({
|
||||||
client.bots
|
id: "create_bot",
|
||||||
.create({ name })
|
onCreate: (bot) => setBots([...(bots ?? []), bot]),
|
||||||
.then(({ bot }) => setBots([...(bots ?? []), bot]))
|
})
|
||||||
}>
|
}
|
||||||
create
|
action="chevron">
|
||||||
</Button>
|
<Text id="app.settings.pages.bots.create_bot" />
|
||||||
</p>
|
</CategoryButton>
|
||||||
<Overline>my bots</Overline>
|
|
||||||
{bots?.map((bot) => {
|
{bots?.map((bot) => {
|
||||||
const user = client.users.get(bot._id);
|
|
||||||
return (
|
return (
|
||||||
<div
|
<BotCard
|
||||||
key={bot._id}
|
key={bot._id}
|
||||||
style={{
|
bot={bot}
|
||||||
background: "var(--secondary-background)",
|
onDelete={() =>
|
||||||
margin: "8px",
|
setBots(bots.filter((x) => x._id !== bot._id))
|
||||||
padding: "12px",
|
}
|
||||||
}}>
|
onUpdate={(changes: Changes) =>
|
||||||
<UserShort user={user} />
|
|
||||||
<p>
|
|
||||||
token:{" "}
|
|
||||||
<code style={{ userSelect: "all" }}>
|
|
||||||
{bot.token}
|
|
||||||
</code>
|
|
||||||
</p>
|
|
||||||
<BotEditor
|
|
||||||
bot={{
|
|
||||||
...bot,
|
|
||||||
username: user!.username,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
error
|
|
||||||
onClick={() =>
|
|
||||||
client.bots
|
|
||||||
.delete(bot._id)
|
|
||||||
.then(() =>
|
|
||||||
setBots(
|
setBots(
|
||||||
bots.filter(
|
bots.map((x) => {
|
||||||
(x) => x._id !== bot._id,
|
if (x._id === bot._id) {
|
||||||
),
|
if (
|
||||||
),
|
"public" in changes &&
|
||||||
|
typeof changes.public === "boolean"
|
||||||
)
|
)
|
||||||
}>
|
x.public = changes.public;
|
||||||
delete
|
if ("interactions_url" in changes)
|
||||||
</Button>
|
x.interactions_url =
|
||||||
<Button
|
changes.interactions_url;
|
||||||
onClick={() =>
|
if (
|
||||||
writeClipboard(
|
changes.remove === "InteractionsURL"
|
||||||
`${window.origin}/bot/${bot._id}`,
|
|
||||||
)
|
)
|
||||||
}>
|
x.interactions_url = undefined;
|
||||||
copy invite link
|
}
|
||||||
</Button>
|
return x;
|
||||||
</div>
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -523,6 +523,94 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.myBots {
|
||||||
|
|
||||||
|
.botCard {
|
||||||
|
background: var(--secondary-background);
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
|
||||||
|
h5 { margin-bottom: 1em }
|
||||||
|
h3 { margin-bottom: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
.botSection {
|
||||||
|
margin: 20px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
label { margin-top: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoheader {
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 5px;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userDetail {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: 2px;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userName {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s ease filter;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(80%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.userid {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
color: var(--tertiary-foreground);
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonRow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue