diff --git a/app/records/_components/ApplyButton.tsx b/app/records/_components/ApplyButton.tsx index 3e68d9f..ff47149 100644 --- a/app/records/_components/ApplyButton.tsx +++ b/app/records/_components/ApplyButton.tsx @@ -1,37 +1,44 @@ "use client" -import { useActionState } from "react" +import { useActionState, useEffect } from "react" import type { ActionState } from "@/lib/action-state" +import { notifyFailure, notifySuccess, notifyWarning } from "@/lib/notify" export interface ApplyButtonProps { action: (prev: ActionState, formData: FormData) => Promise } +// Result messages now arrive as toasts instead of text under the button, so the card never +// grows and nothing has to be dismissed by hand. +const INITIAL: ActionState = { ok: false } + export function ApplyButton({ action }: ApplyButtonProps) { - const [state, formAction, pending] = useActionState(action, { ok: false }) + const [state, formAction, pending] = useActionState(action, INITIAL) + + // Every run of the action returns a fresh object, so this fires exactly once per + // submission — and stays quiet on mount, where `state` carries no message yet. + useEffect(() => { + if (!state.message) return + + // A success that also carries `detail` means the config was written but the restart was + // skipped (see applyConfigAction) — that deserves a warning, not a green tick. When the + // restart failed outright, the detail is the reason, so it joins the message. + if (state.ok) { + if (state.detail) notifyWarning(`${state.message}(${state.detail})`) + else notifySuccess(state.message) + return + } + notifyFailure(state.detail ? `${state.message}:${state.detail}` : state.message) + }, [state]) return ( -
-
- -
- - {state.message ? ( -
-

- {state.message} -

- {state.detail ? ( -

{state.detail}

- ) : null} -
- ) : null} -
+
+ +
) } diff --git a/app/records/_components/DeleteButton.tsx b/app/records/_components/DeleteButton.tsx index 79844ea..7bf6c3d 100644 --- a/app/records/_components/DeleteButton.tsx +++ b/app/records/_components/DeleteButton.tsx @@ -1,5 +1,7 @@ "use client" +import { dangerButtonClass } from "./button-styles" + export function DeleteButton() { return ( ) diff --git a/app/records/_components/RecordForm.tsx b/app/records/_components/RecordForm.tsx index ad4a834..39aefd3 100644 --- a/app/records/_components/RecordForm.tsx +++ b/app/records/_components/RecordForm.tsx @@ -1,9 +1,10 @@ "use client" import Link from "next/link" -import { useActionState } from "react" +import { useActionState, useEffect } from "react" import type { ActionState } from "@/lib/action-state" import type { DnsRecord } from "@/lib/db" +import { notifyFailure } from "@/lib/notify" const fieldClass = "w-full rounded-md border border-black/10 bg-white px-3 py-2 text-sm text-zinc-900 outline-none focus:border-zinc-500 dark:border-white/15 dark:bg-white/5 dark:text-zinc-100" @@ -27,16 +28,16 @@ export function RecordForm({ action, record, submitLabel, cancelHref }: RecordFo const [state, formAction, pending] = useActionState(action, { ok: false }) const errors = state.fieldErrors ?? {} + // Form-level failures (e.g. the record was deleted before the save landed) go out as + // toasts; per-field errors stay inline next to the input they describe. + useEffect(() => { + if (state.message) notifyFailure(state.message) + }, [state]) + return (
{record ? : null} - {state.message ? ( -

- {state.message} -

- ) : null} -