105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
import { useState } from "react";
|
|
import { del, put } from "../../api";
|
|
import { confirmDialog, toast } from "../../dialogs";
|
|
import { useFetch } from "../../hooks";
|
|
import { useT } from "../../i18n";
|
|
import { Icon } from "../Icon";
|
|
|
|
interface Status {
|
|
configured: boolean;
|
|
bot: string;
|
|
paired: boolean;
|
|
pair_code: string;
|
|
running: boolean;
|
|
error?: string | null;
|
|
}
|
|
|
|
/** Connect a personal Telegram bot as a chat interface. */
|
|
export function Telegram() {
|
|
const t = useT();
|
|
const { data, reload } = useFetch<Status>("/account/telegram");
|
|
const [token, setToken] = useState("");
|
|
const [busy, setBusy] = useState(false);
|
|
|
|
const connect = async () => {
|
|
setBusy(true);
|
|
try {
|
|
await put("/account/telegram", { token: token.trim() });
|
|
setToken("");
|
|
reload();
|
|
} catch (e) {
|
|
toast(e);
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
const disconnect = async () => {
|
|
if (!(await confirmDialog({ message: t("telegram.disconnectConfirm"), danger: true, confirmLabel: t("telegram.disconnect") }))) return;
|
|
await del("/account/telegram").catch((e) => toast(e));
|
|
reload();
|
|
};
|
|
|
|
return (
|
|
<div className="panel">
|
|
<h3>
|
|
<Icon name="share" /> {t("telegram.title")}
|
|
<span className="spacer" />
|
|
{data?.configured && (
|
|
<button className="small ghost danger" onClick={disconnect}>
|
|
{t("telegram.disconnect")}
|
|
</button>
|
|
)}
|
|
</h3>
|
|
<div className="hint" style={{ marginBottom: 10 }}>
|
|
{t("telegram.intro")}
|
|
</div>
|
|
{data && !data.configured && (
|
|
<>
|
|
<ol className="cli-steps">
|
|
<li>{t("telegram.step1")}</li>
|
|
<li>{t("telegram.step2")}</li>
|
|
</ol>
|
|
<div className="row" style={{ gap: 6 }}>
|
|
<input
|
|
type="text"
|
|
name="telegram-bot-token"
|
|
value={token}
|
|
placeholder="123456789:AAH…"
|
|
autoComplete="off"
|
|
spellCheck={false}
|
|
className="mono"
|
|
onChange={(e) => setToken(e.target.value)}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
<button className="primary" onClick={() => void connect()} disabled={busy || !token.trim()}>
|
|
{busy ? t("telegram.checking") : t("telegram.connect")}
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
{data?.configured && (
|
|
<>
|
|
<div className="row" style={{ gap: 8, alignItems: "center", flexWrap: "wrap" }}>
|
|
<span className="chip">@{data.bot}</span>
|
|
<span className={`chip ${data.running && !data.error ? "ok" : "warn"}`}>{data.running && !data.error ? t("telegram.running") : t("telegram.stopped")}</span>
|
|
{data.paired && <span className="chip ok">{t("telegram.paired")}</span>}
|
|
</div>
|
|
{!data.paired && data.pair_code && (
|
|
<div className="notice" style={{ marginTop: 10 }}>
|
|
{t("telegram.pairHint", { bot: data.bot })}{" "}
|
|
<a href={`https://t.me/${data.bot}?start=${data.pair_code}`} target="_blank" rel="noreferrer">
|
|
t.me/{data.bot}
|
|
</a>
|
|
<div style={{ marginTop: 6 }}>
|
|
<code className="token-value">/start {data.pair_code}</code>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{data.error && <div className="notice err" style={{ marginTop: 10 }}>{data.error}</div>}
|
|
{data.paired && <div className="hint" style={{ marginTop: 10 }}>{t("telegram.commands")}</div>}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|