Toast action results and confirm destructive actions in one dialog
Inline banners and per-row status text reported action results all over the app; they are replaced by a small toast stack (bottom-right, Level 3 floating well styling) that only speaks for actions: successes fade, errors stay until dismissed, and form-field validation stays inline. Destructive actions no longer use bespoke inline confirm steps (the library detail's Confirm delete button) or fire immediately (duplicate copies/items, delete page selections, temp cleanup, upload discard, job cancellation): they all go through one promise-based confirm dialog (confirmAction) with a danger variant, Escape/backdrop to cancel.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { CheckCircle2, Eye, EyeOff, XCircle } from "lucide-react";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Button, Field, Spinner, inputClass } from "@/components/ui";
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@/lib/e621";
|
||||
import { useAuth } from "@/store/auth";
|
||||
import { useE621 } from "@/store/e621";
|
||||
import { toast } from "@/store/toasts";
|
||||
|
||||
const BASE_URL_OPTIONS = [
|
||||
{ value: "https://e621.net", label: "e621.net — main site" },
|
||||
@@ -33,13 +34,7 @@ function AccountForm({ credentials }: { credentials: E621Credentials | null }) {
|
||||
const [customBase, setCustomBase] = useState(savedBaseIsKnown ? "" : savedBase);
|
||||
const [showKey, setShowKey] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saveMessage, setSaveMessage] = useState<string | null>(null);
|
||||
const [saveError, setSaveError] = useState<string | null>(null);
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [testResult, setTestResult] = useState<{
|
||||
ok: boolean;
|
||||
message: string;
|
||||
} | null>(null);
|
||||
|
||||
const baseUrl = (baseChoice === "custom" ? customBase.trim() : baseChoice).replace(
|
||||
/\/+$/,
|
||||
@@ -53,13 +48,11 @@ function AccountForm({ credentials }: { credentials: E621Credentials | null }) {
|
||||
|
||||
async function handleSave() {
|
||||
setSaving(true);
|
||||
setSaveMessage(null);
|
||||
setSaveError(null);
|
||||
try {
|
||||
await save(values);
|
||||
setSaveMessage("Credentials saved.");
|
||||
toast.ok("e621 credentials saved.");
|
||||
} catch (error) {
|
||||
setSaveError(errorMessage(error));
|
||||
toast.error(errorMessage(error));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -67,19 +60,17 @@ function AccountForm({ credentials }: { credentials: E621Credentials | null }) {
|
||||
|
||||
async function handleTest() {
|
||||
setTesting(true);
|
||||
setTestResult(null);
|
||||
try {
|
||||
const user = await verifyE621Credentials({ ...values, configured: true });
|
||||
setTestResult({
|
||||
ok: true,
|
||||
message: `Connected as ${user.name}${
|
||||
toast.ok(
|
||||
`Connected as ${user.name}${
|
||||
typeof user.favorite_count === "number"
|
||||
? ` — ${user.favorite_count} favorites`
|
||||
: ""
|
||||
}.`,
|
||||
});
|
||||
);
|
||||
} catch (error) {
|
||||
setTestResult({ ok: false, message: e621ErrorMessage(error) });
|
||||
toast.error(e621ErrorMessage(error));
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
@@ -167,32 +158,6 @@ function AccountForm({ credentials }: { credentials: E621Credentials | null }) {
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
{saveError ? (
|
||||
<p className="rounded-md border border-ctp-red/30 bg-ctp-red/10 px-3 py-2 text-sm text-ctp-red">
|
||||
{saveError}
|
||||
</p>
|
||||
) : null}
|
||||
{saveMessage ? (
|
||||
<p className="text-sm text-ctp-green">{saveMessage}</p>
|
||||
) : null}
|
||||
{testResult ? (
|
||||
<p
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded-md border px-3 py-2 text-sm",
|
||||
testResult.ok
|
||||
? "border-ctp-green/30 bg-ctp-green/10 text-ctp-green"
|
||||
: "border-ctp-red/30 bg-ctp-red/10 text-ctp-red",
|
||||
)}
|
||||
>
|
||||
{testResult.ok ? (
|
||||
<CheckCircle2 className="h-4 w-4 shrink-0" />
|
||||
) : (
|
||||
<XCircle className="h-4 w-4 shrink-0" />
|
||||
)}
|
||||
{testResult.message}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Button onClick={() => void handleSave()} disabled={saving}>
|
||||
{saving ? "Saving…" : "Save"}
|
||||
|
||||
Reference in New Issue
Block a user