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:
2026-09-17 22:19:54 -05:00
parent 1053e3ee55
commit e97c3b9da0
21 changed files with 430 additions and 287 deletions
+3 -15
View File
@@ -7,6 +7,7 @@ import { cn } from "@/lib/cn";
import { formatDate } from "@/lib/format";
import type { AdminUser, Paginated } from "@/lib/types";
import { useAuth } from "@/store/auth";
import { toast } from "@/store/toasts";
const ROLE_OPTIONS = [
{ value: "user", label: "User" },
@@ -17,8 +18,6 @@ const ROLE_OPTIONS = [
function UserRow({ profile }: { profile: AdminUser }) {
const queryClient = useQueryClient();
const [avatarJId, setAvatarJId] = useState(profile.avatar_j_id ?? "");
const [message, setMessage] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const mutation = useMutation({
mutationFn: (payload: Record<string, string | null>) =>
@@ -27,15 +26,11 @@ function UserRow({ profile }: { profile: AdminUser }) {
json: payload,
}),
onSuccess: (updated) => {
setMessage("Saved.");
setError(null);
setAvatarJId(updated.avatar_j_id ?? "");
void queryClient.invalidateQueries({ queryKey: ["users"] });
toast.ok(`Saved ${updated.username}.`);
},
onError: (err) => {
setError(errorMessage(err));
setMessage(null);
},
onError: (err) => toast.error(errorMessage(err)),
});
return (
@@ -105,13 +100,6 @@ function UserRow({ profile }: { profile: AdminUser }) {
Save avatar
</Button>
</div>
{message ? (
<p className="text-xs text-ctp-green lg:w-20">{message}</p>
) : null}
{error ? (
<p className="text-xs text-ctp-red lg:w-40">{error}</p>
) : null}
</div>
);
}