"use client"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useRef, useState, useTransition } from "react"; import { toast } from "sonner"; import type { ActionResult } from "@/lib/safe-action-shared"; interface UseServerActionOptions { /** Where to redirect after a successful action (optional) */ redirectOnSuccess?: string; } interface RunOptions { successMessage?: string; errorMessage?: string; /** Override redirect path for this specific call */ redirectTo?: string; /** Called on success with the result data. Overrides default router.refresh(). */ onSuccess?: (data: Record) => void; /** Called when the action fails or throws */ onError?: () => void; } /** * Hook that wraps a server action with loading state, toast notifications, * and optional redirect/refresh. Drop-in replacement for useAdminForm. * * @example * const { run, isPending } = useServerAction() * * // Submit * run(() => updateNews({ id, ...data }), { successMessage: 'Saved!' }) * * // Delete with redirect * run(() => deleteNews(id), { redirectTo: '/admin/news' }) */ export function useServerAction(options: UseServerActionOptions = {}) { const t = useTranslations("pages.admin.actions"); const locked = useRef(false); const [fieldErrors, setFieldErrors] = useState>({}); const [isPending, startTransition] = useTransition(); const router = useRouter(); function run(action: () => Promise, opts: RunOptions = {}) { if (locked.current) return; locked.current = true; setFieldErrors({}); const { successMessage = t("saved"), errorMessage = t("error"), redirectTo, onSuccess, onError, } = opts; startTransition(async () => { try { const result = (await action()) as ActionResult | undefined; if (!result || result.ok) { toast.success(successMessage); if (onSuccess) { onSuccess((result?.data ?? {}) as Record); } else { const target = redirectTo || options.redirectOnSuccess; if (target) router.push(target); else router.refresh(); } } else { setFieldErrors(result.fieldErrors ?? {}); toast.error(result.error || errorMessage, { description: Object.values(result.fieldErrors ?? {}) .flat() .join(" ยท ") || undefined, }); onError?.(); } } catch (err) { console.error("[useServerAction] Unexpected error:", err); toast.error(errorMessage); onError?.(); } finally { locked.current = false; } }); } return { run, isPending, fieldErrors }; }