Files
MineGeo/components/DiscordIntegration.tsx

119 lines
3.4 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { MessageCircle } from "lucide-react";
import { api } from "@/lib/client-api";
import { useI18n } from "./I18nProvider";
import type { DiscordStatus } from "@/shared/discord";
export function DiscordIntegration() {
const { t } = useI18n();
const [status, setStatus] = useState<DiscordStatus | null>(null),
[error, setError] = useState(""),
[message, setMessage] = useState(""),
[busy, setBusy] = useState(false);
async function load() {
try {
setStatus(await api<DiscordStatus>("moderation/discord"));
setError("");
} catch (e) {
setError((e as Error).message);
}
}
useEffect(() => {
void load();
}, []);
const configured = !!(
status?.botConfigured &&
status.publicUrlConfigured &&
(status.mapsConfigured || status.partiesConfigured)
);
async function run(action: "check" | "retry") {
setBusy(true);
setError("");
setMessage("");
try {
await api("moderation/discord", { action });
setMessage(
action === "check"
? "Discord bot and channel access checked. Send Messages and Embed Links are also required."
: "Saved Discord announcements will retry.",
);
await load();
} catch (e) {
setError((e as Error).message);
} finally {
setBusy(false);
}
}
return (
<section className="tool-card" aria-labelledby="discord-integration-title">
<div className="settings-heading">
<MessageCircle size={24} aria-hidden="true" />
<h2 id="discord-integration-title">{t("Discord integration")}</h2>
</div>
<p>
{t(
"New approved maps are announced automatically. Hosts can publish an open party to the shared Discord channel.",
)}
</p>
{status && (
<>
<p className="muted">
{configured
? t("Discord bot configured")
: t("Discord setup required in Vercel environment variables")}
</p>
<p>
{t("Map announcements: {state}", {
state: status.mapsConfigured
? t("Configured")
: t("Not configured"),
})}{" "}
·{" "}
{t("Party announcements: {state}", {
state: status.partiesConfigured
? t("Configured")
: t("Not configured"),
})}
</p>
<p>
{t("{pending} pending · {failed} need attention · {sent} sent", {
pending: status.pending,
failed: status.failed,
sent: status.sent,
})}
</p>
</>
)}
{error && (
<p className="error-message" role="alert">
{t(error)}
</p>
)}
{message && <p role="status">{t(message)}</p>}
<div className="inline-actions">
<button
className="button"
disabled={busy || !configured}
onClick={() => void run("check")}
>
{t("Check Discord setup")}
</button>
<button
className="button"
disabled={busy || !configured}
onClick={() => void run("retry")}
>
{t("Retry saved announcements")}
</button>
<button
className="text-link"
disabled={busy}
onClick={() => void load()}
>
{t("Refresh status")}
</button>
</div>
</section>
);
}