Add MineGeo gameplay systems and interface
This commit is contained in:
118
components/DiscordIntegration.tsx
Normal file
118
components/DiscordIntegration.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user