feat: bot setting
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// import Tippy from '@tippyjs/react';
|
||||
import dayjs from 'dayjs';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetBotAPIKeysQuery } from '../../../app/services/user';
|
||||
import IconDelete from '../../../assets/icons/delete.svg';
|
||||
import CreateAPIKeyModal from './CreateAPIKeyModal';
|
||||
import DeleteAPIKeyModal from './DeleteAPIKeyModal';
|
||||
|
||||
type Props = {
|
||||
uid: number
|
||||
}
|
||||
|
||||
// const APIKeyTable = () => {
|
||||
|
||||
|
||||
// return <table>
|
||||
|
||||
// </table>;
|
||||
// };
|
||||
type DeleteAPIKeyProps = { uid: number, kid: number }
|
||||
const tdClass = "p-1 whitespace-nowrap text-xs text-gray-500 align-middle px-1";
|
||||
const BotAPIKeys = ({ uid }: Props) => {
|
||||
const { t } = useTranslation("setting", { keyPrefix: "bot" });
|
||||
const [currentUid, setCurrentUid] = useState<number | undefined>();
|
||||
const [deleteApiKey, setDeleteApiKey] = useState<DeleteAPIKeyProps | undefined>();
|
||||
const { data, refetch } = useGetBotAPIKeysQuery(uid);
|
||||
const toggleCreateModal = (param?: number) => {
|
||||
if (!param) refetch();
|
||||
setCurrentUid(param);
|
||||
};
|
||||
const toggleDeleteModal = (param?: DeleteAPIKeyProps) => {
|
||||
if (!param) refetch();
|
||||
setDeleteApiKey(param);
|
||||
};
|
||||
if (!data) return null;
|
||||
return (
|
||||
<div className='flex flex-col gap-2 items-start'>
|
||||
<button onClick={toggleCreateModal.bind(null, uid)} type='button' className="rounded-full bg-green-50 text-green-600 text-xs py-0.5 px-2">
|
||||
New API Key
|
||||
</button>
|
||||
{data.length > 0 ? <div className='border-t border-solid border-b border-gray-200 py-2'>
|
||||
<table className="min-w-full table-auto font-mono">
|
||||
<thead >
|
||||
<tr >
|
||||
{[t("col_key_name"), t('col_key_value'), t('col_key_create_time'), t('col_key_last_used'), ""].map(title => <th key={title} scope="col" className="text-xs text-gray-900 px-1 text-left pb-2">
|
||||
{title}
|
||||
</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map(ak => {
|
||||
const { id, name, key, created_at, last_used } = ak;
|
||||
return <tr key={id} className="group" >
|
||||
<td className={tdClass}>
|
||||
{name}
|
||||
</td>
|
||||
<td className={`${tdClass} w-40`}>
|
||||
{`${key.slice(0, 4)} ... ... ${key.slice(-6)}`}
|
||||
</td>
|
||||
<td className={tdClass}>
|
||||
{dayjs(created_at).format("YYYY-MM-DD HH:mm:ss")}
|
||||
</td>
|
||||
<td className={tdClass}>
|
||||
|
||||
{last_used ? dayjs(last_used).format("YYYY-MM-DD HH:mm:ss") : "Unused"}
|
||||
</td>
|
||||
<td className={`${tdClass} invisible group-hover:visible`}>
|
||||
<button onClick={toggleDeleteModal.bind(null, { kid: id, uid })}>
|
||||
<IconDelete />
|
||||
</button>
|
||||
</td>
|
||||
</tr>;
|
||||
})}
|
||||
</tbody>
|
||||
</table></div> : null}
|
||||
{currentUid && <CreateAPIKeyModal uid={currentUid} closeModal={toggleCreateModal.bind(null, undefined)} />}
|
||||
{deleteApiKey && <DeleteAPIKeyModal uid={deleteApiKey.uid} kid={deleteApiKey.kid} closeModal={toggleDeleteModal.bind(null, undefined)} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BotAPIKeys;
|
||||
@@ -0,0 +1,87 @@
|
||||
import { useRef, useEffect } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCreateBotAPIKeyMutation } from '../../../app/services/user';
|
||||
import Modal from '../../../common/component/Modal';
|
||||
import Button from '../../../common/component/styled/Button';
|
||||
import Input from '../../../common/component/styled/Input';
|
||||
import StyledModal from '../../../common/component/styled/Modal';
|
||||
import useCopy from '../../../common/hook/useCopy';
|
||||
|
||||
type Props = {
|
||||
uid: number,
|
||||
closeModal: () => void
|
||||
}
|
||||
const CreateAPIKeyModal = ({ closeModal, uid }: Props) => {
|
||||
const { copy } = useCopy();
|
||||
const [createBotAPIKey, { error, isSuccess, isLoading, data }] = useCreateBotAPIKeyMutation();
|
||||
const formRef = useRef(null);
|
||||
const { t } = useTranslation("setting", { keyPrefix: "bot" });
|
||||
const { t: ct } = useTranslation();
|
||||
// const [input, setInput] = useState("");
|
||||
const handleCreateBot = () => {
|
||||
if (!formRef || !formRef.current) return;
|
||||
const formEle = formRef.current as HTMLFormElement;
|
||||
if (!formEle.checkValidity()) {
|
||||
formEle.reportValidity();
|
||||
return;
|
||||
|
||||
}
|
||||
createBotAPIKey({
|
||||
uid,
|
||||
name: formEle.querySelector('input')?.value || ""
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
switch (error.status) {
|
||||
case 406:
|
||||
toast.error("Invalid Webhook URL!");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [error]);
|
||||
const handleCopy = () => {
|
||||
copy(data);
|
||||
toast.success("API Key Copied!");
|
||||
closeModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal id="modal-modal">
|
||||
<StyledModal
|
||||
title={t("create_key_title")}
|
||||
description={t("create_key_desc")}
|
||||
buttons={
|
||||
isSuccess ? <Button onClick={handleCopy}>
|
||||
{t("key_copy_and_close")}
|
||||
</Button> : <>
|
||||
<Button className="cancel" onClick={closeModal}>
|
||||
{ct("action.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleCreateBot}>{isLoading ? "..." : ct("action.done")}</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{isSuccess ? <div className="flex flex-col gap-2 text-sm">
|
||||
<div className='border-green-600 bg-green-200/50 rounded border border-solid p-2 max-w-md w-full whitespace-pre-wrap break-all' >
|
||||
{data}
|
||||
</div> <div className='text-red-400'>
|
||||
⚠️ {t("create_key_warning")}
|
||||
</div>
|
||||
</div>
|
||||
: <form ref={formRef} className="w-full flex flex-col gap-2 items-center" action="/">
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<label htmlFor={"name"} className="text-sm text-[#6b7280]">Name</label>
|
||||
<Input name={"name"} required placeholder='Please input API Key name'></Input>
|
||||
</div>
|
||||
</form>}
|
||||
</StyledModal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateAPIKeyModal;
|
||||
@@ -0,0 +1,95 @@
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCreateUserMutation } from '../../../app/services/user';
|
||||
import Modal from '../../../common/component/Modal';
|
||||
import Button from '../../../common/component/styled/Button';
|
||||
import Input from '../../../common/component/styled/Input';
|
||||
import StyledModal from '../../../common/component/styled/Modal';
|
||||
|
||||
type Props = {
|
||||
closeModal: () => void
|
||||
}
|
||||
type CreateDTO = {
|
||||
name: string,
|
||||
webhook_url?: string
|
||||
}
|
||||
const CreateModal = ({ closeModal }: Props) => {
|
||||
const [createUser, { isSuccess, isLoading, error }] = useCreateUserMutation();
|
||||
const formRef = useRef(null);
|
||||
const { t } = useTranslation("setting", { keyPrefix: "bot" });
|
||||
const { t: ct } = useTranslation();
|
||||
// const [input, setInput] = useState("");
|
||||
const handleCreateBot = () => {
|
||||
if (!formRef || !formRef.current) return;
|
||||
const formEle = formRef.current as HTMLFormElement;
|
||||
if (!formEle.checkValidity()) {
|
||||
formEle.reportValidity();
|
||||
return;
|
||||
|
||||
}
|
||||
const myFormData = new FormData(formEle);
|
||||
const formDataObj: CreateDTO = { name: "" };
|
||||
myFormData.forEach((value, key) => {
|
||||
if (value) {
|
||||
formDataObj[key] = value;
|
||||
}
|
||||
});
|
||||
createUser({
|
||||
is_bot: true,
|
||||
is_admin: false,
|
||||
gender: 1,
|
||||
email: `bot_${new Date().getTime()}@voce.chat`,
|
||||
password: "",
|
||||
...formDataObj
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
switch (error.status) {
|
||||
case 406:
|
||||
toast.error("Invalid Webhook URL!");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [error]);
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
toast.success("Create Bot Successfully!");
|
||||
closeModal();
|
||||
}
|
||||
}, [isSuccess]);
|
||||
|
||||
return (
|
||||
<Modal id="modal-modal">
|
||||
<StyledModal
|
||||
title={t("create_title")}
|
||||
description={t("create_desc")}
|
||||
buttons={
|
||||
<>
|
||||
<Button className="cancel" onClick={closeModal}>
|
||||
{ct("action.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleCreateBot}>{isLoading ? "Updating" : ct("action.done")}</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<form ref={formRef} className="w-full flex flex-col gap-2 items-center" action="/">
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<label htmlFor={"name"} className="text-sm text-[#6b7280]">Name</label>
|
||||
<Input name={"name"} required placeholder='Please input bot name'></Input>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<label htmlFor={"webhook_url"} className="text-sm text-[#6b7280]">Webhook URL</label>
|
||||
<Input name={"webhook_url"} type="url" placeholder='Please input webhook url'></Input>
|
||||
</div>
|
||||
</form>
|
||||
</StyledModal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateModal;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useEffect } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLazyDeleteBotAPIKeyQuery } from '../../../app/services/user';
|
||||
import Modal from '../../../common/component/Modal';
|
||||
import Button from '../../../common/component/styled/Button';
|
||||
import StyledModal from '../../../common/component/styled/Modal';
|
||||
|
||||
type Props = {
|
||||
uid: number,
|
||||
kid: number,
|
||||
closeModal: () => void
|
||||
}
|
||||
const DeleteAPIKeyModal = ({ closeModal, uid, kid }: Props) => {
|
||||
const [deleteKey, { isSuccess, isLoading }] = useLazyDeleteBotAPIKeyQuery();
|
||||
const { t } = useTranslation("setting", { keyPrefix: "bot" });
|
||||
const { t: ct } = useTranslation();
|
||||
// const [input, setInput] = useState("");
|
||||
const handleDeleteBot = () => {
|
||||
deleteKey({ uid, kid });
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
toast.success("Delete API Key Successfully!");
|
||||
closeModal();
|
||||
}
|
||||
}, [isSuccess]);
|
||||
|
||||
return (
|
||||
<Modal id="modal-modal">
|
||||
<StyledModal
|
||||
title={`${t("delete_key_title")} ${name}`}
|
||||
description={t("delete_key_desc")}
|
||||
buttons={
|
||||
<>
|
||||
<Button className="cancel" onClick={closeModal}>
|
||||
{ct("action.cancel")}
|
||||
</Button>
|
||||
<Button className='danger' onClick={handleDeleteBot}>{isLoading ? "Deleting" : ct("action.done")}</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
</StyledModal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteAPIKeyModal;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useEffect } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLazyDeleteUserQuery } from '../../../app/services/user';
|
||||
import Modal from '../../../common/component/Modal';
|
||||
import Button from '../../../common/component/styled/Button';
|
||||
import StyledModal from '../../../common/component/styled/Modal';
|
||||
|
||||
type Props = {
|
||||
uid: number,
|
||||
name: string,
|
||||
closeModal: () => void
|
||||
}
|
||||
const DeleteModal = ({ closeModal, uid, name }: Props) => {
|
||||
const [deleteUser, { isSuccess, isLoading }] = useLazyDeleteUserQuery();
|
||||
const { t } = useTranslation("setting", { keyPrefix: "bot" });
|
||||
const { t: ct } = useTranslation();
|
||||
// const [input, setInput] = useState("");
|
||||
const handleDeleteBot = () => {
|
||||
deleteUser(uid);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
toast.success("Delete Bot Successfully!");
|
||||
closeModal();
|
||||
}
|
||||
}, [isSuccess]);
|
||||
|
||||
return (
|
||||
<Modal id="modal-modal">
|
||||
<StyledModal
|
||||
title={`${t("delete_title")} ${name}`}
|
||||
description={t("delete_desc")}
|
||||
buttons={
|
||||
<>
|
||||
<Button className="cancel" onClick={closeModal.bind(null, undefined)}>
|
||||
{ct("action.cancel")}
|
||||
</Button>
|
||||
<Button className='danger' onClick={handleDeleteBot}>{isLoading ? "Deleting" : ct("action.done")}</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
</StyledModal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteModal;
|
||||
@@ -0,0 +1,87 @@
|
||||
import clsx from 'clsx';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { Orbit } from "@uiball/loaders";
|
||||
import { useGetUserByAdminQuery, useUpdateUserMutation } from '../../../app/services/user';
|
||||
import IconEdit from '../../../assets/icons/edit.svg';
|
||||
import IconSave from '../../../assets/icons/save.svg';
|
||||
import IconCancel from '../../../assets/icons/close.circle.svg';
|
||||
type Props = {
|
||||
uid: number
|
||||
}
|
||||
|
||||
const WebhookEdit = ({ uid }: Props) => {
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [editable, setEditable] = useState(false);
|
||||
const [url, setUrl] = useState("");
|
||||
const { data, isSuccess, refetch } = useGetUserByAdminQuery(uid);
|
||||
const [updateUser, { isSuccess: updateSuccess, isLoading: isUpdating }] = useUpdateUserMutation();
|
||||
useEffect(() => {
|
||||
if (isSuccess && data) {
|
||||
setUrl(data.webhook_url || "");
|
||||
}
|
||||
}, [data, isSuccess]);
|
||||
useEffect(() => {
|
||||
if (updateSuccess) {
|
||||
refetch();
|
||||
}
|
||||
}, [updateSuccess]);
|
||||
|
||||
const handleEdit = async () => {
|
||||
if (editable && formRef) {
|
||||
const form = formRef.current;
|
||||
// 检查格式
|
||||
if (!form?.checkValidity()) {
|
||||
form?.reportValidity();
|
||||
return;
|
||||
}
|
||||
// 保存编辑
|
||||
const webhook_url = new FormData(form).get("webhook") as string;
|
||||
const resp = await updateUser({ id: uid, webhook_url });
|
||||
console.log("ressssss", resp);
|
||||
if ("error" in resp) {
|
||||
switch (resp.error.status) {
|
||||
case 406:
|
||||
toast.error("Not Valid URL!");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
setEditable(prev => !prev);
|
||||
};
|
||||
const handleEditable = () => {
|
||||
setEditable(true);
|
||||
};
|
||||
const handleCancelEdit = () => {
|
||||
setEditable(false);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{(url || editable || updateSuccess) ?
|
||||
<div className="flex gap-2">
|
||||
<form action="/" ref={formRef} >
|
||||
<input readOnly={!editable} required autoFocus type="url" name='webhook' defaultValue={url} className={clsx("text-sm text-gray-500 px-2 py-1", editable ? "border border-solid border-gray-200 bg-gray-50" : "bg-transparent")} />
|
||||
</form>
|
||||
<button type='button' disabled={isUpdating} onClick={handleEdit}>
|
||||
{isUpdating ? <Orbit size={16} /> : editable ?
|
||||
<IconSave />
|
||||
|
||||
: <IconEdit />}
|
||||
</button>
|
||||
{editable && !isUpdating && <button type='button' disabled={isUpdating} onClick={handleCancelEdit}>
|
||||
<IconCancel className="!w-6 !h-6" />
|
||||
</button>}
|
||||
</div>
|
||||
:
|
||||
<button type='button' className="rounded-full bg-primary-50 text-green-600 text-xs py-0.5 px-2" onClick={handleEditable}>
|
||||
Set Webhook
|
||||
</button>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebhookEdit;
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useRef, useState, useEffect, ChangeEvent } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useUpdateUserMutation } from '../../../app/services/user';
|
||||
import Modal from '../../../common/component/Modal';
|
||||
import Button from '../../../common/component/styled/Button';
|
||||
import Input from '../../../common/component/styled/Input';
|
||||
import StyledModal from '../../../common/component/styled/Modal';
|
||||
|
||||
type Props = {
|
||||
uid: number,
|
||||
webhook?: string,
|
||||
closeModal: () => void
|
||||
}
|
||||
const WebhookModal = ({ uid, webhook, closeModal }: Props) => {
|
||||
const [url, setUrl] = useState(webhook);
|
||||
const [updateUser, { isSuccess, isLoading }] = useUpdateUserMutation();
|
||||
const formRef = useRef(null);
|
||||
const { t } = useTranslation("setting", { keyPrefix: "bot" });
|
||||
const { t: ct } = useTranslation();
|
||||
// const [input, setInput] = useState("");
|
||||
const handleUrlChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
setUrl(evt.target.value);
|
||||
};
|
||||
const handleUpdateWebhook = () => {
|
||||
if (!formRef || !formRef.current) return;
|
||||
const formEle = formRef.current as HTMLFormElement;
|
||||
if (!formEle.checkValidity()) {
|
||||
formEle.reportValidity();
|
||||
return;
|
||||
}
|
||||
const myFormData = new FormData(formEle);
|
||||
const webhook_url = myFormData.get("webhook")?.toString() || "";
|
||||
updateUser({
|
||||
id: uid,
|
||||
webhook_url
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
toast.success("Update Webhook URL Successfully!");
|
||||
closeModal();
|
||||
}
|
||||
}, [isSuccess]);
|
||||
|
||||
return (
|
||||
<Modal id="modal-modal">
|
||||
<StyledModal
|
||||
title={t("webhook_title")}
|
||||
description={t("webhook_desc")}
|
||||
buttons={
|
||||
<>
|
||||
<Button className="cancel" onClick={closeModal.bind(null, undefined)}>
|
||||
{ct("action.cancel")}
|
||||
</Button>
|
||||
<Button disabled={!url} onClick={handleUpdateWebhook}>{isLoading ? "Updating" : ct("action.done")}</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<form ref={formRef} className="w-full flex flex-col gap-2" action="/">
|
||||
<label htmlFor={"webhook"} className="text-sm text-[#6b7280]">Webhook URL</label>
|
||||
<Input name={"webhook"} value={url} onChange={handleUrlChange} type="url"></Input>
|
||||
</form>
|
||||
</StyledModal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebhookModal;
|
||||
@@ -0,0 +1,112 @@
|
||||
import { useEffect } from "react";
|
||||
// import dayjs from "dayjs";
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useUpdateAvatarByAdminMutation } from '../../../app/services/user';
|
||||
import { useAppSelector } from '../../../app/store';
|
||||
import AvatarUploader from '../../../common/component/AvatarUploader';
|
||||
import Button from '../../../common/component/styled/Button';
|
||||
import IconDelete from '../../../assets/icons/delete.svg';
|
||||
import CreateModal from './CreateModal';
|
||||
import WebhookEdit from './WebhookEdit';
|
||||
import WebhookModal from './WebhookModal';
|
||||
import DeleteModal from './DeleteModal';
|
||||
import BotAPIKeys from './BotAPIKeys';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
type TipProps = { title: string, desc: string };
|
||||
const Tip = ({ title, desc }: TipProps) => {
|
||||
|
||||
return <div className="flex flex-col text-sm">
|
||||
<h2 className="">{title}</h2>
|
||||
<p className="text-gray-500">{desc}</p>
|
||||
</div>;
|
||||
};
|
||||
const tdClass = "px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 align-top";
|
||||
type WebhookParams = { webhook?: string, uid: number };
|
||||
type DeleteParams = { name: string, uid: number };
|
||||
export default function BotConfig() {
|
||||
const [updateAvatar, { isSuccess: updateAvatarSuccess }] = useUpdateAvatarByAdminMutation();
|
||||
const [createModalVisible, setCreateModalVisible] = useState(false);
|
||||
const [currWebhookParams, setCurrWebhookParams] = useState<WebhookParams | undefined>(undefined);
|
||||
const [currDeleteParams, setCurrDeleteParams] = useState<DeleteParams | undefined>(undefined);
|
||||
const bots = useAppSelector(store => Object.values(store.users.byId).filter(u => !!u.is_bot));
|
||||
const { t } = useTranslation("setting", { keyPrefix: "bot" });
|
||||
|
||||
const toggleCreateModalVisible = () => {
|
||||
setCreateModalVisible(prev => !prev);
|
||||
};
|
||||
const toggleWebhookModalVisible = (obj?: WebhookParams) => {
|
||||
console.log("webhook modal", obj);
|
||||
|
||||
setCurrWebhookParams(obj);
|
||||
};
|
||||
const toggleDeleteModalVisible = (obj?: DeleteParams) => {
|
||||
setCurrDeleteParams(obj);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (updateAvatarSuccess) {
|
||||
toast.success("Update Bot Avatar Successfully!");
|
||||
}
|
||||
}, [updateAvatarSuccess]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col justify-start items-start gap-4">
|
||||
<div className="flex flex-col gap-4 max-w-[634px] mb-4">
|
||||
<Tip title={t("bot_tip_title")} desc={t("bot_tip_desc")} />
|
||||
<Tip title={t("webhook_tip_title")} desc={t("webhook_tip_desc")} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h2 className='font-semibold'>{t("manage")}</h2>
|
||||
<p className='text-gray-500 text-xs'>{t("manage_desc")}</p>
|
||||
</div>
|
||||
|
||||
<div className="w-fit overflow-hidden">
|
||||
<table className="min-w-full table-auto">
|
||||
<thead className="border-b bg-gray-50">
|
||||
<tr>
|
||||
{[t("col_avatar"), t('col_name'), t('col_api_key'), t('col_webhook'), t('col_opt')].map(title => <th key={title} scope="col" className="text-sm font-bold text-gray-900 px-6 py-4 text-left">
|
||||
{title}
|
||||
</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{bots.map(bot => {
|
||||
const { uid, name, avatar } = bot;
|
||||
return <tr key={uid} className="bg-white border-b transition duration-300 ease-in-out hover:bg-gray-100">
|
||||
<td className="px-4 py-2 flex flex-col gap-1 items-start">
|
||||
<AvatarUploader uid={uid} url={avatar} uploadImage={updateAvatar} name={name} className="!w-14 !h-14" />
|
||||
<span className='text-xs text-gray-500'>
|
||||
#{uid}
|
||||
</span>
|
||||
</td>
|
||||
<td className={tdClass}>
|
||||
{name}
|
||||
</td>
|
||||
<td className={tdClass}>
|
||||
<BotAPIKeys uid={uid} />
|
||||
</td>
|
||||
<td className={tdClass}>
|
||||
<WebhookEdit uid={uid} />
|
||||
</td>
|
||||
<td className={tdClass}>
|
||||
<button type='button' onClick={toggleDeleteModalVisible.bind(null, { uid, name })} >
|
||||
<IconDelete className="hover:opacity-80" />
|
||||
</button>
|
||||
{/* <Button className='mini' onClick={toggleWebhookModalVisible.bind(null, { webhook: webhook_url, uid })} >Set Webhook</Button> */}
|
||||
</td>
|
||||
</tr>;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<Button onClick={toggleCreateModalVisible} className="ghost small">Add</Button>
|
||||
|
||||
</div>
|
||||
{createModalVisible && <CreateModal closeModal={toggleCreateModalVisible} />}
|
||||
{currWebhookParams && <WebhookModal closeModal={toggleWebhookModalVisible} {...currWebhookParams} />}
|
||||
{currDeleteParams && <DeleteModal closeModal={toggleDeleteModalVisible} {...currDeleteParams} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import ConfigSMTP from "./config/SMTP";
|
||||
import APIConfig from "./APIConfig";
|
||||
import License from "./License/License";
|
||||
import Widget from "./Widget";
|
||||
import BotConfig from "./BotConfig";
|
||||
import APIDocument from "./APIDocument";
|
||||
import ManageMembers from "../../common/component/ManageMembers";
|
||||
import FAQ from "../../common/component/FAQ";
|
||||
@@ -35,6 +36,11 @@ const navs = [
|
||||
{
|
||||
title: "config",
|
||||
items: [
|
||||
{
|
||||
name: "bot",
|
||||
component: <BotConfig />,
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: "firebase",
|
||||
component: <ConfigFirebase />
|
||||
|
||||
Reference in New Issue
Block a user