mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 06:15:39 -04:00
Add confirmation toasts to settings actions and fix webhook card spacing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { IconWebhook } from "@tabler/icons-react";
|
import { IconWebhook } from "@tabler/icons-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
deleteWebhookConnection,
|
deleteWebhookConnection,
|
||||||
regenerateWebhookToken,
|
regenerateWebhookToken,
|
||||||
@@ -22,40 +23,57 @@ export function IntegrationsSection({
|
|||||||
connections.find((c) => c.provider === "jellyfin") ?? null;
|
connections.find((c) => c.provider === "jellyfin") ?? null;
|
||||||
|
|
||||||
async function handleSave(provider: "plex" | "jellyfin", username: string) {
|
async function handleSave(provider: "plex" | "jellyfin", username: string) {
|
||||||
const result = await saveWebhookConnection(provider, username);
|
const label = provider === "plex" ? "Plex" : "Jellyfin";
|
||||||
setConnections((prev) => {
|
const isNew = !connections.find((c) => c.provider === provider);
|
||||||
const existing = prev.find((c) => c.provider === provider);
|
try {
|
||||||
if (existing) {
|
const result = await saveWebhookConnection(provider, username);
|
||||||
return prev.map((c) =>
|
setConnections((prev) => {
|
||||||
c.provider === provider
|
const existing = prev.find((c) => c.provider === provider);
|
||||||
? { ...result, recentEvents: existing.recentEvents }
|
if (existing) {
|
||||||
: c,
|
return prev.map((c) =>
|
||||||
);
|
c.provider === provider
|
||||||
}
|
? { ...result, recentEvents: existing.recentEvents }
|
||||||
return [...prev, { ...result, recentEvents: [] }];
|
: c,
|
||||||
});
|
);
|
||||||
|
}
|
||||||
|
return [...prev, { ...result, recentEvents: [] }];
|
||||||
|
});
|
||||||
|
toast.success(isNew ? `${label} connected` : `${label} updated`);
|
||||||
|
} catch {
|
||||||
|
toast.error(`Failed to save ${label} connection`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDelete(provider: "plex" | "jellyfin") {
|
async function handleDelete(provider: "plex" | "jellyfin") {
|
||||||
|
const label = provider === "plex" ? "Plex" : "Jellyfin";
|
||||||
const previous = connections;
|
const previous = connections;
|
||||||
setConnections((prev) => prev.filter((c) => c.provider !== provider));
|
setConnections((prev) => prev.filter((c) => c.provider !== provider));
|
||||||
try {
|
try {
|
||||||
await deleteWebhookConnection(provider);
|
await deleteWebhookConnection(provider);
|
||||||
|
toast.success(`${label} disconnected`);
|
||||||
} catch {
|
} catch {
|
||||||
setConnections(previous);
|
setConnections(previous);
|
||||||
|
toast.error(`Failed to disconnect ${label}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRegenerateToken(provider: "plex" | "jellyfin") {
|
async function handleRegenerateToken(provider: "plex" | "jellyfin") {
|
||||||
const result = await regenerateWebhookToken(provider);
|
const label = provider === "plex" ? "Plex" : "Jellyfin";
|
||||||
setConnections((prev) =>
|
try {
|
||||||
prev.map((c) =>
|
const result = await regenerateWebhookToken(provider);
|
||||||
c.provider === provider ? { ...c, token: result.token } : c,
|
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`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleToggle(provider: "plex" | "jellyfin", enabled: boolean) {
|
async function handleToggle(provider: "plex" | "jellyfin", enabled: boolean) {
|
||||||
|
const label = provider === "plex" ? "Plex" : "Jellyfin";
|
||||||
const previous = connections;
|
const previous = connections;
|
||||||
setConnections((prev) =>
|
setConnections((prev) =>
|
||||||
prev.map((c) => (c.provider === provider ? { ...c, enabled } : c)),
|
prev.map((c) => (c.provider === provider ? { ...c, enabled } : c)),
|
||||||
@@ -67,8 +85,10 @@ export function IntegrationsSection({
|
|||||||
conn?.mediaServerUsername ?? "",
|
conn?.mediaServerUsername ?? "",
|
||||||
enabled,
|
enabled,
|
||||||
);
|
);
|
||||||
|
toast.success(`${label} webhook ${enabled ? "enabled" : "disabled"}`);
|
||||||
} catch {
|
} catch {
|
||||||
setConnections(previous);
|
setConnections(previous);
|
||||||
|
toast.error(`Failed to update ${label}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { IconShieldLock, IconUserPlus } from "@tabler/icons-react";
|
import { IconServerCog, IconUserPlus } from "@tabler/icons-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -27,8 +28,10 @@ export function ServerSection({
|
|||||||
setToggling(true);
|
setToggling(true);
|
||||||
try {
|
try {
|
||||||
await toggleRegistration(checked);
|
await toggleRegistration(checked);
|
||||||
|
toast.success(checked ? "Registration opened" : "Registration closed");
|
||||||
} catch {
|
} catch {
|
||||||
setRegistrationOpen(previous);
|
setRegistrationOpen(previous);
|
||||||
|
toast.error("Failed to update registration setting");
|
||||||
} finally {
|
} finally {
|
||||||
setToggling(false);
|
setToggling(false);
|
||||||
}
|
}
|
||||||
@@ -37,12 +40,12 @@ export function ServerSection({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-3 flex items-center gap-2">
|
<div className="mb-3 flex items-center gap-2">
|
||||||
<IconShieldLock size={16} className="text-muted-foreground" />
|
<IconServerCog size={16} className="text-muted-foreground" />
|
||||||
<h2 className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
<h2 className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||||
Server
|
Server
|
||||||
</h2>
|
</h2>
|
||||||
<span className="rounded-md bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary">
|
<span className="rounded-md bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary">
|
||||||
Admin
|
Admin only
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Card className="border-l-2 border-l-primary/30">
|
<Card className="border-l-2 border-l-primary/30">
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export function WebhookCard({
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<CardContent className="space-y-3 pt-0">
|
<CardContent className="space-y-3 border-t border-border/30 pt-4">
|
||||||
{connection && (
|
{connection && (
|
||||||
<div className="flex items-center justify-between rounded-lg bg-muted/30 px-3 py-2">
|
<div className="flex items-center justify-between rounded-lg bg-muted/30 px-3 py-2">
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
|
|||||||
Reference in New Issue
Block a user