import { useEffect, useState } from "react"; import { toast } from "sonner"; import { useTranslation } from "@termix/plugin-sdk/frontend"; import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, PasswordInput, Switch, Textarea, cn, } from "@termix/plugin-sdk/ui"; import { CHANNEL_TYPES, type ChannelType } from "../types"; import { EMPTY, configFrom, draftFrom, type Draft } from "./channel-config"; import type { AlertsApi } from "./api"; function Field({ label, hint, children, }: { label: string; hint?: string; children: React.ReactNode; }) { return (
{children} {hint && ( {hint} )}
); } export function ChannelDialog({ api, open, channelId, emailAvailable, onOpenChange, onSaved, }: { api: AlertsApi; open: boolean; /** Null to create a new channel. */ channelId: number | null; emailAvailable: boolean; onOpenChange: (open: boolean) => void; onSaved: () => void; }) { const { t } = useTranslation(); const [draft, setDraft] = useState(EMPTY); const [saving, setSaving] = useState(false); useEffect(() => { if (!open) return; if (channelId === null) { setDraft(EMPTY); return; } api .channel(channelId) .then((channel) => setDraft(draftFrom(channel))) .catch((error) => toast.error(error instanceof Error ? error.message : String(error)), ); }, [api, channelId, open]); const set = (key: K, value: Draft[K]) => setDraft((current) => ({ ...current, [key]: value })); const pickType = (type: ChannelType) => setDraft((current) => ({ ...current, type, url: type === "ntfy" && !current.url ? "https://ntfy.sh" : type !== "ntfy" && current.url === "https://ntfy.sh" ? "" : current.url, })); const save = async () => { if (!draft.name.trim()) { toast.error(t("channels.nameRequired")); return; } setSaving(true); try { const input = { name: draft.name.trim(), type: draft.type, config: configFrom(draft), }; if (channelId === null) await api.createChannel(input); else await api.updateChannel(channelId, input); onSaved(); } catch (error) { toast.error(error instanceof Error ? error.message : String(error)); } finally { setSaving(false); } }; const canReachPrivate = draft.type === "webhook" || draft.type === "ntfy"; return ( {channelId === null ? t("channels.add") : t("channels.edit")}
set("name", event.target.value)} className="text-sm rounded-none" />
{CHANNEL_TYPES.map((type) => { const disabled = type === "email" && !emailAvailable; return ( ); })}
{draft.type === "email" && !emailAvailable && ( {t("channels.emailUnavailable")} )}
{draft.type === "webhook" && ( <> set("url", event.target.value)} placeholder="https://example.com/webhook" className="text-sm font-mono rounded-none" />
{(["POST", "PUT"] as const).map((method) => ( ))}