Files
sofa/lib/atoms/integrations.ts
T
jake dfc0f67527 Migrate component state to Jotai atoms across settings, explore, and dashboard
Replace ad-hoc useState/useEffect fetch patterns with Jotai atoms and
loadables in StatsDisplay, FilterableTitleRow, BackupScheduleSection,
IntegrationsSection, and CommandPalette. Each component now gets a
scoped Jotai Provider with a pre-initialized store so server-rendered
initial values hydrate correctly. Async data fetching moves into
atom-level loadables, eliminating manual loading flags and cancellation
logic throughout.
2026-03-05 10:09:47 -05:00

87 lines
2.6 KiB
TypeScript

import { atom, useAtom } from "jotai";
import { useCallback } from "react";
import { toast } from "sonner";
import type { WebhookConnection } from "@/app/(pages)/settings/_components/webhook-card";
import {
deleteWebhookConnection,
regenerateWebhookToken,
saveWebhookConnection,
} from "@/lib/actions/settings";
export const connectionsAtom = atom<WebhookConnection[]>([]);
function providerLabel(provider: "plex" | "jellyfin" | "emby") {
return provider === "plex"
? "Plex"
: provider === "emby"
? "Emby"
: "Jellyfin";
}
export function useConnectionActions(provider: "plex" | "jellyfin" | "emby") {
const [connections, setConnections] = useAtom(connectionsAtom);
const label = providerLabel(provider);
const connection = connections.find((c) => c.provider === provider) ?? null;
const handleConnect = useCallback(async () => {
try {
const result = await saveWebhookConnection(provider);
setConnections((prev) => [...prev, { ...result, recentEvents: [] }]);
toast.success(`${label} connected`);
} catch {
toast.error(`Failed to connect ${label}`);
}
}, [provider, label, setConnections]);
const handleDelete = useCallback(async () => {
const previous = connections;
setConnections((prev) => prev.filter((c) => c.provider !== provider));
try {
await deleteWebhookConnection(provider);
toast.success(`${label} disconnected`);
} catch {
setConnections(previous);
toast.error(`Failed to disconnect ${label}`);
}
}, [provider, label, connections, setConnections]);
const handleRegenerateToken = useCallback(async () => {
try {
const result = await regenerateWebhookToken(provider);
setConnections((prev) =>
prev.map((c) =>
c.provider === provider ? { ...c, token: result.token } : c,
),
);
toast.success(`${label} webhook URL regenerated`);
} catch {
toast.error(`Failed to regenerate ${label} URL`);
}
}, [provider, label, setConnections]);
const handleToggle = useCallback(
async (enabled: boolean) => {
const previous = connections;
setConnections((prev) =>
prev.map((c) => (c.provider === provider ? { ...c, enabled } : c)),
);
try {
await saveWebhookConnection(provider, enabled);
toast.success(`${label} webhook ${enabled ? "enabled" : "disabled"}`);
} catch {
setConnections(previous);
toast.error(`Failed to update ${label}`);
}
},
[provider, label, connections, setConnections],
);
return {
connection,
handleConnect,
handleDelete,
handleRegenerateToken,
handleToggle,
};
}