next-action-forge 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,7 @@ A powerful, type-safe toolkit for Next.js server actions with Zod validation and
14
14
  - 🎨 **Custom error handling** with flexible error transformation
15
15
  - 🦆 **Duck-typed errors** - Any error with `toServerActionError()` method is automatically handled
16
16
  - 📋 **Smart FormData parsing** - Handles arrays, checkboxes, and files correctly
17
+ - 🍞 **Persistent toast messages** - Toast notifications survive page redirects (v0.2.0+)
17
18
  - ✅ **React 19 & Next.js 15** compatible
18
19
 
19
20
  ## 📦 Installation
@@ -281,6 +282,62 @@ export const updateProfileAction = actionClient
281
282
  });
282
283
  ```
283
284
 
285
+ ### Persistent Toast Messages (v0.2.0+)
286
+
287
+ Toast notifications now automatically persist across page redirects, perfect for authentication errors:
288
+
289
+ ```tsx
290
+ "use client";
291
+
292
+ import { useServerAction } from "next-action-forge/hooks";
293
+ import { ToastRestorer } from "next-action-forge/hooks";
294
+ import { deletePost } from "@/app/actions/posts";
295
+
296
+ // Add ToastRestorer to your root layout
297
+ export function RootLayout({ children }: { children: React.ReactNode }) {
298
+ return (
299
+ <html>
300
+ <body>
301
+ <ToastRestorer />
302
+ {children}
303
+ </body>
304
+ </html>
305
+ );
306
+ }
307
+
308
+ // Toast messages survive redirects automatically
309
+ export function DeleteButton({ postId }: { postId: string }) {
310
+ const { execute, isExecuting } = useServerAction(deletePost, {
311
+ showErrorToast: true, // Error toasts persist through redirects
312
+ });
313
+
314
+ return (
315
+ <button onClick={() => execute({ postId })} disabled={isExecuting}>
316
+ Delete Post
317
+ </button>
318
+ );
319
+ }
320
+
321
+ // Server action that triggers redirect on auth error
322
+ export const deletePost = authClient
323
+ .inputSchema(z.object({ postId: z.string() }))
324
+ .action(async ({ postId }, context) => {
325
+ if (!context.user) {
326
+ // This error message will show after redirect to login
327
+ throw new Error("You must be logged in to delete posts");
328
+ }
329
+
330
+ await db.post.delete({ where: { id: postId } });
331
+ return { success: true };
332
+ });
333
+ ```
334
+
335
+ **Features:**
336
+ - Zero configuration - just add `ToastRestorer` to your layout
337
+ - Compatible with future Sonner persistent toast feature
338
+ - Automatically cleans up old toasts (30 seconds expiry)
339
+ - Works with all redirect scenarios
340
+
284
341
  ### Custom Error Classes
285
342
 
286
343
  ```typescript
@@ -98,4 +98,6 @@ interface UseFormActionReturn<TFieldValues extends FieldValues, TOutput> {
98
98
  }
99
99
  declare function useFormAction<TFieldValues extends FieldValues = FieldValues, TOutput = void>({ action, schema, defaultValues, mode, transformData, onSuccess, onError, resetOnSuccess, showSuccessToast, showErrorToast, }: UseFormActionOptions<TFieldValues, TOutput>): UseFormActionReturn<TFieldValues, TOutput>;
100
100
 
101
- export { type UseFormActionOptions, type UseFormActionReturn, type UseOptimisticActionOptions, type UseOptimisticActionReturn, type UseServerActionOptions, type UseServerActionReturn, useFormAction, useOptimisticAction, useServerAction };
101
+ declare function ToastRestorer(): null;
102
+
103
+ export { ToastRestorer, type UseFormActionOptions, type UseFormActionReturn, type UseOptimisticActionOptions, type UseOptimisticActionReturn, type UseServerActionOptions, type UseServerActionReturn, useFormAction, useOptimisticAction, useServerAction };
@@ -98,4 +98,6 @@ interface UseFormActionReturn<TFieldValues extends FieldValues, TOutput> {
98
98
  }
99
99
  declare function useFormAction<TFieldValues extends FieldValues = FieldValues, TOutput = void>({ action, schema, defaultValues, mode, transformData, onSuccess, onError, resetOnSuccess, showSuccessToast, showErrorToast, }: UseFormActionOptions<TFieldValues, TOutput>): UseFormActionReturn<TFieldValues, TOutput>;
100
100
 
101
- export { type UseFormActionOptions, type UseFormActionReturn, type UseOptimisticActionOptions, type UseOptimisticActionReturn, type UseServerActionOptions, type UseServerActionReturn, useFormAction, useOptimisticAction, useServerAction };
101
+ declare function ToastRestorer(): null;
102
+
103
+ export { ToastRestorer, type UseFormActionOptions, type UseFormActionReturn, type UseOptimisticActionOptions, type UseOptimisticActionReturn, type UseServerActionOptions, type UseServerActionReturn, useFormAction, useOptimisticAction, useServerAction };
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/hooks/index.ts
22
22
  var hooks_exports = {};
23
23
  __export(hooks_exports, {
24
+ ToastRestorer: () => ToastRestorer,
24
25
  useFormAction: () => useFormAction,
25
26
  useOptimisticAction: () => useOptimisticAction,
26
27
  useServerAction: () => useServerAction
@@ -74,6 +75,29 @@ function useServerAction(action, options = {}) {
74
75
  console.log("[useServerAction] Redirect to:", response.error.redirectTo);
75
76
  }
76
77
  if (redirectOnAuthError && response.error.shouldRedirect) {
78
+ if (showErrorToast) {
79
+ const message = errorMessage ? typeof errorMessage === "function" ? errorMessage(actionResult) : errorMessage : response.error.message || "An error occurred";
80
+ if (typeof window !== "undefined") {
81
+ try {
82
+ const storageKey = "sonner-toasts";
83
+ const existingToasts = JSON.parse(
84
+ localStorage.getItem(storageKey) || "[]"
85
+ );
86
+ const persistentToast = {
87
+ id: Date.now(),
88
+ type: "error",
89
+ message,
90
+ persistent: true,
91
+ createdAt: Date.now()
92
+ };
93
+ existingToasts.push(persistentToast);
94
+ localStorage.setItem(storageKey, JSON.stringify(existingToasts));
95
+ } catch (err) {
96
+ console.error("Failed to persist toast:", err);
97
+ }
98
+ }
99
+ import_sonner.toast.error(message, { persistent: true });
100
+ }
77
101
  router.push(response.error.redirectTo || "/login");
78
102
  return actionResult;
79
103
  }
@@ -269,8 +293,51 @@ function useFormAction({
269
293
  handleSubmit
270
294
  };
271
295
  }
296
+
297
+ // src/hooks/toast-restorer.tsx
298
+ var import_react4 = require("react");
299
+ var import_sonner3 = require("sonner");
300
+ function ToastRestorer() {
301
+ (0, import_react4.useEffect)(() => {
302
+ if (typeof window === "undefined") return;
303
+ const storageKey = "sonner-toasts";
304
+ try {
305
+ const storedToasts = localStorage.getItem(storageKey);
306
+ if (storedToasts) {
307
+ const persistentToasts = JSON.parse(storedToasts);
308
+ if (Array.isArray(persistentToasts) && persistentToasts.length > 0) {
309
+ localStorage.removeItem(storageKey);
310
+ const recentToasts = persistentToasts.filter(
311
+ (t) => Date.now() - (t.createdAt || 0) < 3e4
312
+ );
313
+ recentToasts.forEach((persistedToast, index) => {
314
+ if (persistedToast.type === "loading") return;
315
+ setTimeout(() => {
316
+ const toastFunction = import_sonner3.toast[persistedToast.type];
317
+ if (typeof toastFunction === "function") {
318
+ if (toastFunction.length >= 2) {
319
+ toastFunction(persistedToast.message, {
320
+ persistent: true,
321
+ id: persistedToast.id
322
+ });
323
+ } else {
324
+ toastFunction(persistedToast.message);
325
+ }
326
+ }
327
+ }, index * 150);
328
+ });
329
+ }
330
+ }
331
+ } catch (error) {
332
+ console.error("Failed to restore toasts:", error);
333
+ localStorage.removeItem(storageKey);
334
+ }
335
+ }, []);
336
+ return null;
337
+ }
272
338
  // Annotate the CommonJS export names for ESM import in node:
273
339
  0 && (module.exports = {
340
+ ToastRestorer,
274
341
  useFormAction,
275
342
  useOptimisticAction,
276
343
  useServerAction
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/use-server-action.ts","../../src/hooks/use-optimistic-action.ts","../../src/hooks/use-form-action.ts"],"sourcesContent":["export * from \"./use-server-action\";\nexport * from \"./use-optimistic-action\";\nexport * from \"./use-form-action\";","import { useCallback, useState, useTransition } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { toast } from \"sonner\";\nimport type { ServerAction, ActionResult, ServerActionResponse } from \"../types\";\n\nexport interface UseServerActionOptions<TOutput> {\n onSuccess?: (data: TOutput) => void | Promise<void>;\n onError?: (error: ActionResult<TOutput>) => void | Promise<void>;\n \n successMessage?: string | ((data: TOutput) => string);\n errorMessage?: string | ((error: ActionResult<TOutput>) => string);\n showSuccessToast?: boolean;\n showErrorToast?: boolean;\n \n redirectOnAuthError?: boolean;\n}\n\nexport interface UseServerActionReturn<TInput, TOutput> {\n execute: TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n result: ActionResult<TOutput> | undefined;\n isExecuting: boolean;\n hasSucceeded: boolean;\n hasErrored: boolean;\n \n reset: () => void;\n}\n\nexport function useServerAction<TInput, TOutput>(\n action: ServerAction<TInput, TOutput>,\n options: UseServerActionOptions<TOutput> = {}\n): UseServerActionReturn<TInput, TOutput> {\n const router = useRouter();\n const [result, setResult] = useState<ActionResult<TOutput>>();\n const [isExecuting, setIsExecuting] = useState(false);\n const [, startTransition] = useTransition();\n \n const {\n onSuccess,\n onError,\n successMessage,\n errorMessage,\n showSuccessToast = false,\n showErrorToast = true,\n redirectOnAuthError = true,\n } = options;\n \n const hasSucceeded = !!result?.data;\n const hasErrored = !!result?.serverError || !!result?.validationErrors || !!result?.fetchError;\n \n const reset = useCallback(() => {\n setResult(undefined);\n }, []);\n \n const execute = useCallback(\n async (input?: TInput): Promise<ActionResult<TOutput>> => {\n setIsExecuting(true);\n \n try {\n const response = await (input === undefined \n ? (action as () => Promise<ServerActionResponse<TOutput>>)()\n : (action as (input: TInput) => Promise<ServerActionResponse<TOutput>>)(input));\n \n let actionResult: ActionResult<TOutput>;\n \n if (response.success && response.data !== undefined) {\n actionResult = { data: response.data };\n \n if (showSuccessToast && successMessage) {\n const message = typeof successMessage === \"function\" \n ? successMessage(response.data) \n : successMessage;\n toast.success(message);\n }\n \n await onSuccess?.(response.data);\n \n } else if (!response.success && response.error) {\n actionResult = {\n serverError: response.error,\n validationErrors: response.error.fields,\n };\n \n // Debug logging\n if (process.env.NODE_ENV === 'development') {\n console.log('[useServerAction] Error response:', response.error);\n console.log('[useServerAction] Should redirect?', response.error.shouldRedirect);\n console.log('[useServerAction] Redirect to:', response.error.redirectTo);\n }\n \n if (redirectOnAuthError && response.error.shouldRedirect) {\n router.push(response.error.redirectTo || \"/login\");\n return actionResult;\n }\n \n if (showErrorToast) {\n const message = errorMessage \n ? (typeof errorMessage === \"function\" \n ? errorMessage(actionResult) \n : errorMessage)\n : response.error.message || \"An error occurred\";\n toast.error(message);\n }\n \n await onError?.(actionResult);\n } else {\n actionResult = { fetchError: \"Unexpected response format\" };\n }\n \n setResult(actionResult);\n return actionResult;\n \n } catch (error) {\n const fetchError = error instanceof Error ? error.message : \"Network error\";\n const actionResult: ActionResult<TOutput> = { fetchError };\n \n setResult(actionResult);\n \n if (showErrorToast) {\n toast.error(fetchError);\n }\n \n await onError?.(actionResult);\n return actionResult;\n } finally {\n setIsExecuting(false);\n }\n },\n [action, router, showSuccessToast, successMessage, showErrorToast, errorMessage, redirectOnAuthError, onSuccess, onError]\n );\n \n const executeWithTransition = useCallback(\n (input?: TInput): Promise<ActionResult<TOutput>> => {\n return new Promise((resolve) => {\n startTransition(async () => {\n const result = await execute(input);\n resolve(result);\n });\n });\n },\n [execute]\n ) as any;\n \n return {\n execute: executeWithTransition,\n result,\n isExecuting,\n hasSucceeded,\n hasErrored,\n reset,\n };\n}","import { useOptimistic, useTransition } from \"react\";\nimport { useServerAction, type UseServerActionOptions } from \"./use-server-action\";\nimport type { ServerAction, ActionResult } from \"../types\";\n\nexport interface UseOptimisticActionOptions<TInput, TOutput, TOptimistic> extends UseServerActionOptions<TOutput> {\n /**\n * Function to update the optimistic state\n */\n updateFn: TInput extends void \n ? (current: TOptimistic) => TOptimistic\n : (current: TOptimistic, input: TInput) => TOptimistic;\n}\n\nexport interface UseOptimisticActionReturn<TInput, TOutput, TOptimistic> {\n /**\n * The optimistic state\n */\n optimisticState: TOptimistic;\n \n /**\n * Execute the action with optimistic update\n */\n execute: TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n /**\n * Loading state\n */\n isExecuting: boolean;\n \n /**\n * Success state\n */\n hasSucceeded: boolean;\n \n /**\n * Error state\n */\n hasErrored: boolean;\n \n /**\n * Last result\n */\n result: ActionResult<TOutput> | undefined;\n \n /**\n * Reset state\n */\n reset: () => void;\n}\n\n/**\n * Hook for server actions with optimistic updates\n * \n * @example\n * ```typescript\n * const { optimisticState, execute } = useOptimisticAction(\n * currentTodos,\n * addTodoAction,\n * {\n * updateFn: (todos, newTodo) => [...todos, newTodo],\n * onSuccess: (savedTodo) => {\n * // Update with server response if needed\n * }\n * }\n * );\n * ```\n */\nexport function useOptimisticAction<TInput, TOutput, TOptimistic>(\n initialState: TOptimistic,\n action: ServerAction<TInput, TOutput>,\n options: UseOptimisticActionOptions<TInput, TOutput, TOptimistic>\n): UseOptimisticActionReturn<TInput, TOutput, TOptimistic> {\n const [optimisticState, addOptimisticUpdate] = useOptimistic(\n initialState,\n options.updateFn\n );\n \n const [, startTransition] = useTransition();\n \n // Extract updateFn from options to pass the rest to useServerAction\n const { updateFn: _, ...serverActionOptions } = options;\n \n const {\n execute: executeAction,\n isExecuting,\n hasSucceeded,\n hasErrored,\n result,\n reset\n } = useServerAction(action, serverActionOptions);\n \n const execute = (async (input?: TInput): Promise<ActionResult<TOutput>> => {\n // Apply optimistic update\n startTransition(() => {\n if (input !== undefined) {\n addOptimisticUpdate(input);\n } else {\n addOptimisticUpdate(undefined as any);\n }\n });\n \n // Execute the server action\n return executeAction(input as any);\n }) as any;\n \n return {\n optimisticState,\n execute,\n isExecuting,\n hasSucceeded,\n hasErrored,\n result,\n reset\n };\n}","import { useActionState, useEffect, useTransition } from \"react\";\nimport type { UseFormProps, UseFormReturn, Path, FieldValues, DefaultValues } from \"react-hook-form\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { toast } from \"sonner\";\nimport type { z } from \"zod\";\nimport type { ServerActionResponse, ServerActionError } from \"../types\";\n\nexport interface UseFormActionOptions<TFieldValues extends FieldValues, TOutput> {\n // Required - the server action to execute (form action format)\n action: (prevState: ServerActionResponse<TOutput>, formData: FormData) => Promise<ServerActionResponse<TOutput>>;\n \n // Optional Zod schema for validation\n schema?: z.ZodTypeAny;\n \n // React Hook Form options\n defaultValues?: DefaultValues<TFieldValues>;\n mode?: UseFormProps<TFieldValues>[\"mode\"];\n \n // Transform function if you need custom FormData creation\n transformData?: (data: TFieldValues) => FormData;\n \n // Callbacks\n onSuccess?: (data: TOutput) => void | Promise<void>;\n onError?: (error: ServerActionError) => void;\n \n // Behavior options\n resetOnSuccess?: boolean;\n showSuccessToast?: boolean | string | ((data: TOutput) => string);\n showErrorToast?: boolean | string | ((error: ServerActionError) => string);\n}\n\nexport interface UseFormActionReturn<TFieldValues extends FieldValues, TOutput> {\n // React Hook Form instance\n form: UseFormReturn<TFieldValues>;\n \n // Submit handler (pre-bound with handleSubmit)\n onSubmit: (e?: React.BaseSyntheticEvent) => void;\n \n // Loading state (combines isPending and isTransitioning)\n isSubmitting: boolean;\n \n // The current action state\n actionState: ServerActionResponse<TOutput>;\n \n // Utilities\n reset: () => void;\n \n // Raw handlers if needed\n handleSubmit: (data: TFieldValues) => Promise<void>;\n}\n\nfunction isSuccessResponse<T>(response: ServerActionResponse<T>): response is { success: true; data: T } {\n return response.success === true;\n}\n\nfunction objectToFormData(obj: any): FormData {\n const formData = new FormData();\n \n Object.entries(obj).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n if (value instanceof File || value instanceof Blob) {\n formData.append(key, value);\n } else if (Array.isArray(value)) {\n value.forEach((item) => formData.append(`${key}[]`, String(item)));\n } else if (typeof value === \"object\") {\n formData.append(key, JSON.stringify(value));\n } else {\n formData.append(key, String(value));\n }\n }\n });\n \n return formData;\n}\n\nexport function useFormAction<TFieldValues extends FieldValues = FieldValues, TOutput = void>({\n action,\n schema,\n defaultValues,\n mode = \"onChange\",\n transformData,\n onSuccess,\n onError,\n resetOnSuccess = false,\n showSuccessToast = false,\n showErrorToast = true,\n}: UseFormActionOptions<TFieldValues, TOutput>): UseFormActionReturn<TFieldValues, TOutput> {\n // 1. Setup form with React Hook Form\n const form = useForm<TFieldValues>({\n resolver: schema ? (zodResolver as any)(schema) : undefined,\n defaultValues,\n mode,\n } as UseFormProps<TFieldValues>);\n \n // 2. Setup server action state with useActionState\n const initialState: ServerActionResponse<TOutput> = { success: true, data: undefined as TOutput };\n const [actionState, formAction, isPending] = useActionState(action, initialState);\n const [isTransitioning, startTransition] = useTransition();\n \n // 3. Handle server errors and success\n useEffect(() => {\n if (!isSuccessResponse(actionState) && actionState.error) {\n const { error } = actionState;\n \n // Map field errors to form\n if (error.fields) {\n Object.entries(error.fields).forEach(([field, messages]) => {\n if (Array.isArray(messages) && messages.length > 0) {\n form.setError(field as Path<TFieldValues>, {\n type: \"server\",\n message: messages[0],\n });\n }\n });\n }\n \n // Single field error\n if (error.field && error.message && !error.fields) {\n form.setError(error.field as Path<TFieldValues>, {\n type: \"server\",\n message: error.message,\n });\n }\n \n // Global error (no specific field)\n if (error.message && !error.field && !error.fields) {\n if (showErrorToast) {\n const message = typeof showErrorToast === \"function\" \n ? showErrorToast(error)\n : typeof showErrorToast === \"string\"\n ? showErrorToast\n : error.message;\n toast.error(message);\n }\n \n // Also set on root for inline display\n form.setError(\"root\", {\n type: \"server\",\n message: error.message,\n });\n }\n \n // Call error callback\n onError?.(error);\n }\n \n // Handle success\n if (isSuccessResponse(actionState) && actionState.data !== undefined) {\n if (showSuccessToast) {\n const message = typeof showSuccessToast === \"function\"\n ? showSuccessToast(actionState.data)\n : typeof showSuccessToast === \"string\"\n ? showSuccessToast\n : \"Success!\";\n toast.success(message);\n }\n \n if (resetOnSuccess) {\n form.reset();\n }\n \n // Call success callback\n onSuccess?.(actionState.data);\n }\n }, [actionState, form, onError, onSuccess, resetOnSuccess, showErrorToast, showSuccessToast]);\n \n // 4. Submit handler\n const handleSubmit = async (data: TFieldValues): Promise<void> => {\n // Clear any previous errors\n form.clearErrors();\n \n // Transform to FormData\n const formData = transformData \n ? transformData(data)\n : objectToFormData(data);\n \n // Execute with transition for better UX\n startTransition(() => {\n formAction(formData);\n });\n };\n \n // 5. Combined loading state\n const isSubmitting = isPending || isTransitioning;\n \n // 6. Pre-bound submit handler\n const onSubmit = (e?: React.BaseSyntheticEvent): void => {\n e?.preventDefault();\n void form.handleSubmit(handleSubmit)(e!);\n };\n \n return {\n form,\n onSubmit,\n isSubmitting,\n actionState,\n reset: form.reset,\n handleSubmit,\n };\n}"],"mappings":"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","names":["result","import_react","import_react","import_sonner"]}
1
+ {"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/use-server-action.ts","../../src/hooks/use-optimistic-action.ts","../../src/hooks/use-form-action.ts","../../src/hooks/toast-restorer.tsx"],"sourcesContent":["export * from \"./use-server-action\";\nexport * from \"./use-optimistic-action\";\nexport * from \"./use-form-action\";\nexport { ToastRestorer } from \"./toast-restorer\";","import { useCallback, useState, useTransition } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { toast } from \"sonner\";\nimport type { ServerAction, ActionResult, ServerActionResponse } from \"../types\";\n\nexport interface UseServerActionOptions<TOutput> {\n onSuccess?: (data: TOutput) => void | Promise<void>;\n onError?: (error: ActionResult<TOutput>) => void | Promise<void>;\n \n successMessage?: string | ((data: TOutput) => string);\n errorMessage?: string | ((error: ActionResult<TOutput>) => string);\n showSuccessToast?: boolean;\n showErrorToast?: boolean;\n \n redirectOnAuthError?: boolean;\n}\n\nexport interface UseServerActionReturn<TInput, TOutput> {\n execute: TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n result: ActionResult<TOutput> | undefined;\n isExecuting: boolean;\n hasSucceeded: boolean;\n hasErrored: boolean;\n \n reset: () => void;\n}\n\nexport function useServerAction<TInput, TOutput>(\n action: ServerAction<TInput, TOutput>,\n options: UseServerActionOptions<TOutput> = {}\n): UseServerActionReturn<TInput, TOutput> {\n const router = useRouter();\n const [result, setResult] = useState<ActionResult<TOutput>>();\n const [isExecuting, setIsExecuting] = useState(false);\n const [, startTransition] = useTransition();\n \n const {\n onSuccess,\n onError,\n successMessage,\n errorMessage,\n showSuccessToast = false,\n showErrorToast = true,\n redirectOnAuthError = true,\n } = options;\n \n const hasSucceeded = !!result?.data;\n const hasErrored = !!result?.serverError || !!result?.validationErrors || !!result?.fetchError;\n \n const reset = useCallback(() => {\n setResult(undefined);\n }, []);\n \n const execute = useCallback(\n async (input?: TInput): Promise<ActionResult<TOutput>> => {\n setIsExecuting(true);\n \n try {\n const response = await (input === undefined \n ? (action as () => Promise<ServerActionResponse<TOutput>>)()\n : (action as (input: TInput) => Promise<ServerActionResponse<TOutput>>)(input));\n \n let actionResult: ActionResult<TOutput>;\n \n if (response.success && response.data !== undefined) {\n actionResult = { data: response.data };\n \n if (showSuccessToast && successMessage) {\n const message = typeof successMessage === \"function\" \n ? successMessage(response.data) \n : successMessage;\n toast.success(message);\n }\n \n await onSuccess?.(response.data);\n \n } else if (!response.success && response.error) {\n actionResult = {\n serverError: response.error,\n validationErrors: response.error.fields,\n };\n \n // Debug logging\n if (process.env.NODE_ENV === 'development') {\n console.log('[useServerAction] Error response:', response.error);\n console.log('[useServerAction] Should redirect?', response.error.shouldRedirect);\n console.log('[useServerAction] Redirect to:', response.error.redirectTo);\n }\n \n if (redirectOnAuthError && response.error.shouldRedirect) {\n // Handle toast before redirect\n if (showErrorToast) {\n const message = errorMessage \n ? (typeof errorMessage === \"function\" \n ? errorMessage(actionResult) \n : errorMessage)\n : response.error.message || \"An error occurred\";\n \n // Always save to localStorage for redirect cases\n // This ensures compatibility with both current and future sonner versions\n if (typeof window !== 'undefined') {\n try {\n const storageKey = 'sonner-toasts';\n const existingToasts = JSON.parse(\n localStorage.getItem(storageKey) || '[]'\n );\n \n // Add our toast in the same format as the PR\n const persistentToast = {\n id: Date.now(),\n type: 'error',\n message,\n persistent: true,\n createdAt: Date.now()\n };\n \n existingToasts.push(persistentToast);\n localStorage.setItem(storageKey, JSON.stringify(existingToasts));\n } catch (err) {\n console.error('Failed to persist toast:', err);\n }\n }\n \n // Always show the toast (with persistent if supported)\n (toast.error as any)(message, { persistent: true });\n }\n \n router.push(response.error.redirectTo || \"/login\");\n return actionResult;\n }\n \n if (showErrorToast) {\n const message = errorMessage \n ? (typeof errorMessage === \"function\" \n ? errorMessage(actionResult) \n : errorMessage)\n : response.error.message || \"An error occurred\";\n toast.error(message);\n }\n \n await onError?.(actionResult);\n } else {\n actionResult = { fetchError: \"Unexpected response format\" };\n }\n \n setResult(actionResult);\n return actionResult;\n \n } catch (error) {\n const fetchError = error instanceof Error ? error.message : \"Network error\";\n const actionResult: ActionResult<TOutput> = { fetchError };\n \n setResult(actionResult);\n \n if (showErrorToast) {\n toast.error(fetchError);\n }\n \n await onError?.(actionResult);\n return actionResult;\n } finally {\n setIsExecuting(false);\n }\n },\n [action, router, showSuccessToast, successMessage, showErrorToast, errorMessage, redirectOnAuthError, onSuccess, onError]\n );\n \n const executeWithTransition = useCallback(\n (input?: TInput): Promise<ActionResult<TOutput>> => {\n return new Promise((resolve) => {\n startTransition(async () => {\n const result = await execute(input);\n resolve(result);\n });\n });\n },\n [execute]\n ) as any;\n \n return {\n execute: executeWithTransition,\n result,\n isExecuting,\n hasSucceeded,\n hasErrored,\n reset,\n };\n}","import { useOptimistic, useTransition } from \"react\";\nimport { useServerAction, type UseServerActionOptions } from \"./use-server-action\";\nimport type { ServerAction, ActionResult } from \"../types\";\n\nexport interface UseOptimisticActionOptions<TInput, TOutput, TOptimistic> extends UseServerActionOptions<TOutput> {\n /**\n * Function to update the optimistic state\n */\n updateFn: TInput extends void \n ? (current: TOptimistic) => TOptimistic\n : (current: TOptimistic, input: TInput) => TOptimistic;\n}\n\nexport interface UseOptimisticActionReturn<TInput, TOutput, TOptimistic> {\n /**\n * The optimistic state\n */\n optimisticState: TOptimistic;\n \n /**\n * Execute the action with optimistic update\n */\n execute: TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n /**\n * Loading state\n */\n isExecuting: boolean;\n \n /**\n * Success state\n */\n hasSucceeded: boolean;\n \n /**\n * Error state\n */\n hasErrored: boolean;\n \n /**\n * Last result\n */\n result: ActionResult<TOutput> | undefined;\n \n /**\n * Reset state\n */\n reset: () => void;\n}\n\n/**\n * Hook for server actions with optimistic updates\n * \n * @example\n * ```typescript\n * const { optimisticState, execute } = useOptimisticAction(\n * currentTodos,\n * addTodoAction,\n * {\n * updateFn: (todos, newTodo) => [...todos, newTodo],\n * onSuccess: (savedTodo) => {\n * // Update with server response if needed\n * }\n * }\n * );\n * ```\n */\nexport function useOptimisticAction<TInput, TOutput, TOptimistic>(\n initialState: TOptimistic,\n action: ServerAction<TInput, TOutput>,\n options: UseOptimisticActionOptions<TInput, TOutput, TOptimistic>\n): UseOptimisticActionReturn<TInput, TOutput, TOptimistic> {\n const [optimisticState, addOptimisticUpdate] = useOptimistic(\n initialState,\n options.updateFn\n );\n \n const [, startTransition] = useTransition();\n \n // Extract updateFn from options to pass the rest to useServerAction\n const { updateFn: _, ...serverActionOptions } = options;\n \n const {\n execute: executeAction,\n isExecuting,\n hasSucceeded,\n hasErrored,\n result,\n reset\n } = useServerAction(action, serverActionOptions);\n \n const execute = (async (input?: TInput): Promise<ActionResult<TOutput>> => {\n // Apply optimistic update\n startTransition(() => {\n if (input !== undefined) {\n addOptimisticUpdate(input);\n } else {\n addOptimisticUpdate(undefined as any);\n }\n });\n \n // Execute the server action\n return executeAction(input as any);\n }) as any;\n \n return {\n optimisticState,\n execute,\n isExecuting,\n hasSucceeded,\n hasErrored,\n result,\n reset\n };\n}","import { useActionState, useEffect, useTransition } from \"react\";\nimport type { UseFormProps, UseFormReturn, Path, FieldValues, DefaultValues } from \"react-hook-form\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { toast } from \"sonner\";\nimport type { z } from \"zod\";\nimport type { ServerActionResponse, ServerActionError } from \"../types\";\n\nexport interface UseFormActionOptions<TFieldValues extends FieldValues, TOutput> {\n // Required - the server action to execute (form action format)\n action: (prevState: ServerActionResponse<TOutput>, formData: FormData) => Promise<ServerActionResponse<TOutput>>;\n \n // Optional Zod schema for validation\n schema?: z.ZodTypeAny;\n \n // React Hook Form options\n defaultValues?: DefaultValues<TFieldValues>;\n mode?: UseFormProps<TFieldValues>[\"mode\"];\n \n // Transform function if you need custom FormData creation\n transformData?: (data: TFieldValues) => FormData;\n \n // Callbacks\n onSuccess?: (data: TOutput) => void | Promise<void>;\n onError?: (error: ServerActionError) => void;\n \n // Behavior options\n resetOnSuccess?: boolean;\n showSuccessToast?: boolean | string | ((data: TOutput) => string);\n showErrorToast?: boolean | string | ((error: ServerActionError) => string);\n}\n\nexport interface UseFormActionReturn<TFieldValues extends FieldValues, TOutput> {\n // React Hook Form instance\n form: UseFormReturn<TFieldValues>;\n \n // Submit handler (pre-bound with handleSubmit)\n onSubmit: (e?: React.BaseSyntheticEvent) => void;\n \n // Loading state (combines isPending and isTransitioning)\n isSubmitting: boolean;\n \n // The current action state\n actionState: ServerActionResponse<TOutput>;\n \n // Utilities\n reset: () => void;\n \n // Raw handlers if needed\n handleSubmit: (data: TFieldValues) => Promise<void>;\n}\n\nfunction isSuccessResponse<T>(response: ServerActionResponse<T>): response is { success: true; data: T } {\n return response.success === true;\n}\n\nfunction objectToFormData(obj: any): FormData {\n const formData = new FormData();\n \n Object.entries(obj).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n if (value instanceof File || value instanceof Blob) {\n formData.append(key, value);\n } else if (Array.isArray(value)) {\n value.forEach((item) => formData.append(`${key}[]`, String(item)));\n } else if (typeof value === \"object\") {\n formData.append(key, JSON.stringify(value));\n } else {\n formData.append(key, String(value));\n }\n }\n });\n \n return formData;\n}\n\nexport function useFormAction<TFieldValues extends FieldValues = FieldValues, TOutput = void>({\n action,\n schema,\n defaultValues,\n mode = \"onChange\",\n transformData,\n onSuccess,\n onError,\n resetOnSuccess = false,\n showSuccessToast = false,\n showErrorToast = true,\n}: UseFormActionOptions<TFieldValues, TOutput>): UseFormActionReturn<TFieldValues, TOutput> {\n // 1. Setup form with React Hook Form\n const form = useForm<TFieldValues>({\n resolver: schema ? (zodResolver as any)(schema) : undefined,\n defaultValues,\n mode,\n } as UseFormProps<TFieldValues>);\n \n // 2. Setup server action state with useActionState\n const initialState: ServerActionResponse<TOutput> = { success: true, data: undefined as TOutput };\n const [actionState, formAction, isPending] = useActionState(action, initialState);\n const [isTransitioning, startTransition] = useTransition();\n \n // 3. Handle server errors and success\n useEffect(() => {\n if (!isSuccessResponse(actionState) && actionState.error) {\n const { error } = actionState;\n \n // Map field errors to form\n if (error.fields) {\n Object.entries(error.fields).forEach(([field, messages]) => {\n if (Array.isArray(messages) && messages.length > 0) {\n form.setError(field as Path<TFieldValues>, {\n type: \"server\",\n message: messages[0],\n });\n }\n });\n }\n \n // Single field error\n if (error.field && error.message && !error.fields) {\n form.setError(error.field as Path<TFieldValues>, {\n type: \"server\",\n message: error.message,\n });\n }\n \n // Global error (no specific field)\n if (error.message && !error.field && !error.fields) {\n if (showErrorToast) {\n const message = typeof showErrorToast === \"function\" \n ? showErrorToast(error)\n : typeof showErrorToast === \"string\"\n ? showErrorToast\n : error.message;\n toast.error(message);\n }\n \n // Also set on root for inline display\n form.setError(\"root\", {\n type: \"server\",\n message: error.message,\n });\n }\n \n // Call error callback\n onError?.(error);\n }\n \n // Handle success\n if (isSuccessResponse(actionState) && actionState.data !== undefined) {\n if (showSuccessToast) {\n const message = typeof showSuccessToast === \"function\"\n ? showSuccessToast(actionState.data)\n : typeof showSuccessToast === \"string\"\n ? showSuccessToast\n : \"Success!\";\n toast.success(message);\n }\n \n if (resetOnSuccess) {\n form.reset();\n }\n \n // Call success callback\n onSuccess?.(actionState.data);\n }\n }, [actionState, form, onError, onSuccess, resetOnSuccess, showErrorToast, showSuccessToast]);\n \n // 4. Submit handler\n const handleSubmit = async (data: TFieldValues): Promise<void> => {\n // Clear any previous errors\n form.clearErrors();\n \n // Transform to FormData\n const formData = transformData \n ? transformData(data)\n : objectToFormData(data);\n \n // Execute with transition for better UX\n startTransition(() => {\n formAction(formData);\n });\n };\n \n // 5. Combined loading state\n const isSubmitting = isPending || isTransitioning;\n \n // 6. Pre-bound submit handler\n const onSubmit = (e?: React.BaseSyntheticEvent): void => {\n e?.preventDefault();\n void form.handleSubmit(handleSubmit)(e!);\n };\n \n return {\n form,\n onSubmit,\n isSubmitting,\n actionState,\n reset: form.reset,\n handleSubmit,\n };\n}","\"use client\";\n\nimport { useEffect } from \"react\";\nimport { toast } from \"sonner\";\n\nexport function ToastRestorer() {\n useEffect(() => {\n // Only run on client side\n if (typeof window === \"undefined\") return;\n \n const storageKey = \"sonner-toasts\";\n \n try {\n const storedToasts = localStorage.getItem(storageKey);\n \n if (storedToasts) {\n const persistentToasts = JSON.parse(storedToasts);\n \n if (Array.isArray(persistentToasts) && persistentToasts.length > 0) {\n // Clear the storage immediately to prevent duplicate toasts\n localStorage.removeItem(storageKey);\n \n // Filter out old toasts (older than 30 seconds)\n const recentToasts = persistentToasts.filter(\n (t: any) => Date.now() - (t.createdAt || 0) < 30000\n );\n \n // Restore toasts with staggered animation like in the PR\n recentToasts.forEach((persistedToast: any, index: number) => {\n // Skip loading toasts as per the PR implementation\n if (persistedToast.type === \"loading\") return;\n \n setTimeout(() => {\n const toastFunction = toast[persistedToast.type as keyof typeof toast];\n \n if (typeof toastFunction === \"function\") {\n // Check if current sonner supports persistent\n if (toastFunction.length >= 2) {\n // Use persistent if available\n (toastFunction as any)(persistedToast.message, { \n persistent: true,\n id: persistedToast.id \n });\n } else {\n // Fallback to normal toast\n toastFunction(persistedToast.message);\n }\n }\n }, index * 150); // Staggered delay like in the PR\n });\n }\n }\n } catch (error) {\n console.error(\"Failed to restore toasts:\", error);\n // Clean up on error\n localStorage.removeItem(storageKey);\n }\n }, []);\n \n return null;\n}"],"mappings":"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","names":["result","import_react","import_react","import_sonner","import_react","import_sonner"]}
@@ -46,6 +46,29 @@ function useServerAction(action, options = {}) {
46
46
  console.log("[useServerAction] Redirect to:", response.error.redirectTo);
47
47
  }
48
48
  if (redirectOnAuthError && response.error.shouldRedirect) {
49
+ if (showErrorToast) {
50
+ const message = errorMessage ? typeof errorMessage === "function" ? errorMessage(actionResult) : errorMessage : response.error.message || "An error occurred";
51
+ if (typeof window !== "undefined") {
52
+ try {
53
+ const storageKey = "sonner-toasts";
54
+ const existingToasts = JSON.parse(
55
+ localStorage.getItem(storageKey) || "[]"
56
+ );
57
+ const persistentToast = {
58
+ id: Date.now(),
59
+ type: "error",
60
+ message,
61
+ persistent: true,
62
+ createdAt: Date.now()
63
+ };
64
+ existingToasts.push(persistentToast);
65
+ localStorage.setItem(storageKey, JSON.stringify(existingToasts));
66
+ } catch (err) {
67
+ console.error("Failed to persist toast:", err);
68
+ }
69
+ }
70
+ toast.error(message, { persistent: true });
71
+ }
49
72
  router.push(response.error.redirectTo || "/login");
50
73
  return actionResult;
51
74
  }
@@ -241,7 +264,50 @@ function useFormAction({
241
264
  handleSubmit
242
265
  };
243
266
  }
267
+
268
+ // src/hooks/toast-restorer.tsx
269
+ import { useEffect as useEffect2 } from "react";
270
+ import { toast as toast3 } from "sonner";
271
+ function ToastRestorer() {
272
+ useEffect2(() => {
273
+ if (typeof window === "undefined") return;
274
+ const storageKey = "sonner-toasts";
275
+ try {
276
+ const storedToasts = localStorage.getItem(storageKey);
277
+ if (storedToasts) {
278
+ const persistentToasts = JSON.parse(storedToasts);
279
+ if (Array.isArray(persistentToasts) && persistentToasts.length > 0) {
280
+ localStorage.removeItem(storageKey);
281
+ const recentToasts = persistentToasts.filter(
282
+ (t) => Date.now() - (t.createdAt || 0) < 3e4
283
+ );
284
+ recentToasts.forEach((persistedToast, index) => {
285
+ if (persistedToast.type === "loading") return;
286
+ setTimeout(() => {
287
+ const toastFunction = toast3[persistedToast.type];
288
+ if (typeof toastFunction === "function") {
289
+ if (toastFunction.length >= 2) {
290
+ toastFunction(persistedToast.message, {
291
+ persistent: true,
292
+ id: persistedToast.id
293
+ });
294
+ } else {
295
+ toastFunction(persistedToast.message);
296
+ }
297
+ }
298
+ }, index * 150);
299
+ });
300
+ }
301
+ }
302
+ } catch (error) {
303
+ console.error("Failed to restore toasts:", error);
304
+ localStorage.removeItem(storageKey);
305
+ }
306
+ }, []);
307
+ return null;
308
+ }
244
309
  export {
310
+ ToastRestorer,
245
311
  useFormAction,
246
312
  useOptimisticAction,
247
313
  useServerAction
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/use-server-action.ts","../../src/hooks/use-optimistic-action.ts","../../src/hooks/use-form-action.ts"],"sourcesContent":["import { useCallback, useState, useTransition } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { toast } from \"sonner\";\nimport type { ServerAction, ActionResult, ServerActionResponse } from \"../types\";\n\nexport interface UseServerActionOptions<TOutput> {\n onSuccess?: (data: TOutput) => void | Promise<void>;\n onError?: (error: ActionResult<TOutput>) => void | Promise<void>;\n \n successMessage?: string | ((data: TOutput) => string);\n errorMessage?: string | ((error: ActionResult<TOutput>) => string);\n showSuccessToast?: boolean;\n showErrorToast?: boolean;\n \n redirectOnAuthError?: boolean;\n}\n\nexport interface UseServerActionReturn<TInput, TOutput> {\n execute: TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n result: ActionResult<TOutput> | undefined;\n isExecuting: boolean;\n hasSucceeded: boolean;\n hasErrored: boolean;\n \n reset: () => void;\n}\n\nexport function useServerAction<TInput, TOutput>(\n action: ServerAction<TInput, TOutput>,\n options: UseServerActionOptions<TOutput> = {}\n): UseServerActionReturn<TInput, TOutput> {\n const router = useRouter();\n const [result, setResult] = useState<ActionResult<TOutput>>();\n const [isExecuting, setIsExecuting] = useState(false);\n const [, startTransition] = useTransition();\n \n const {\n onSuccess,\n onError,\n successMessage,\n errorMessage,\n showSuccessToast = false,\n showErrorToast = true,\n redirectOnAuthError = true,\n } = options;\n \n const hasSucceeded = !!result?.data;\n const hasErrored = !!result?.serverError || !!result?.validationErrors || !!result?.fetchError;\n \n const reset = useCallback(() => {\n setResult(undefined);\n }, []);\n \n const execute = useCallback(\n async (input?: TInput): Promise<ActionResult<TOutput>> => {\n setIsExecuting(true);\n \n try {\n const response = await (input === undefined \n ? (action as () => Promise<ServerActionResponse<TOutput>>)()\n : (action as (input: TInput) => Promise<ServerActionResponse<TOutput>>)(input));\n \n let actionResult: ActionResult<TOutput>;\n \n if (response.success && response.data !== undefined) {\n actionResult = { data: response.data };\n \n if (showSuccessToast && successMessage) {\n const message = typeof successMessage === \"function\" \n ? successMessage(response.data) \n : successMessage;\n toast.success(message);\n }\n \n await onSuccess?.(response.data);\n \n } else if (!response.success && response.error) {\n actionResult = {\n serverError: response.error,\n validationErrors: response.error.fields,\n };\n \n // Debug logging\n if (process.env.NODE_ENV === 'development') {\n console.log('[useServerAction] Error response:', response.error);\n console.log('[useServerAction] Should redirect?', response.error.shouldRedirect);\n console.log('[useServerAction] Redirect to:', response.error.redirectTo);\n }\n \n if (redirectOnAuthError && response.error.shouldRedirect) {\n router.push(response.error.redirectTo || \"/login\");\n return actionResult;\n }\n \n if (showErrorToast) {\n const message = errorMessage \n ? (typeof errorMessage === \"function\" \n ? errorMessage(actionResult) \n : errorMessage)\n : response.error.message || \"An error occurred\";\n toast.error(message);\n }\n \n await onError?.(actionResult);\n } else {\n actionResult = { fetchError: \"Unexpected response format\" };\n }\n \n setResult(actionResult);\n return actionResult;\n \n } catch (error) {\n const fetchError = error instanceof Error ? error.message : \"Network error\";\n const actionResult: ActionResult<TOutput> = { fetchError };\n \n setResult(actionResult);\n \n if (showErrorToast) {\n toast.error(fetchError);\n }\n \n await onError?.(actionResult);\n return actionResult;\n } finally {\n setIsExecuting(false);\n }\n },\n [action, router, showSuccessToast, successMessage, showErrorToast, errorMessage, redirectOnAuthError, onSuccess, onError]\n );\n \n const executeWithTransition = useCallback(\n (input?: TInput): Promise<ActionResult<TOutput>> => {\n return new Promise((resolve) => {\n startTransition(async () => {\n const result = await execute(input);\n resolve(result);\n });\n });\n },\n [execute]\n ) as any;\n \n return {\n execute: executeWithTransition,\n result,\n isExecuting,\n hasSucceeded,\n hasErrored,\n reset,\n };\n}","import { useOptimistic, useTransition } from \"react\";\nimport { useServerAction, type UseServerActionOptions } from \"./use-server-action\";\nimport type { ServerAction, ActionResult } from \"../types\";\n\nexport interface UseOptimisticActionOptions<TInput, TOutput, TOptimistic> extends UseServerActionOptions<TOutput> {\n /**\n * Function to update the optimistic state\n */\n updateFn: TInput extends void \n ? (current: TOptimistic) => TOptimistic\n : (current: TOptimistic, input: TInput) => TOptimistic;\n}\n\nexport interface UseOptimisticActionReturn<TInput, TOutput, TOptimistic> {\n /**\n * The optimistic state\n */\n optimisticState: TOptimistic;\n \n /**\n * Execute the action with optimistic update\n */\n execute: TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n /**\n * Loading state\n */\n isExecuting: boolean;\n \n /**\n * Success state\n */\n hasSucceeded: boolean;\n \n /**\n * Error state\n */\n hasErrored: boolean;\n \n /**\n * Last result\n */\n result: ActionResult<TOutput> | undefined;\n \n /**\n * Reset state\n */\n reset: () => void;\n}\n\n/**\n * Hook for server actions with optimistic updates\n * \n * @example\n * ```typescript\n * const { optimisticState, execute } = useOptimisticAction(\n * currentTodos,\n * addTodoAction,\n * {\n * updateFn: (todos, newTodo) => [...todos, newTodo],\n * onSuccess: (savedTodo) => {\n * // Update with server response if needed\n * }\n * }\n * );\n * ```\n */\nexport function useOptimisticAction<TInput, TOutput, TOptimistic>(\n initialState: TOptimistic,\n action: ServerAction<TInput, TOutput>,\n options: UseOptimisticActionOptions<TInput, TOutput, TOptimistic>\n): UseOptimisticActionReturn<TInput, TOutput, TOptimistic> {\n const [optimisticState, addOptimisticUpdate] = useOptimistic(\n initialState,\n options.updateFn\n );\n \n const [, startTransition] = useTransition();\n \n // Extract updateFn from options to pass the rest to useServerAction\n const { updateFn: _, ...serverActionOptions } = options;\n \n const {\n execute: executeAction,\n isExecuting,\n hasSucceeded,\n hasErrored,\n result,\n reset\n } = useServerAction(action, serverActionOptions);\n \n const execute = (async (input?: TInput): Promise<ActionResult<TOutput>> => {\n // Apply optimistic update\n startTransition(() => {\n if (input !== undefined) {\n addOptimisticUpdate(input);\n } else {\n addOptimisticUpdate(undefined as any);\n }\n });\n \n // Execute the server action\n return executeAction(input as any);\n }) as any;\n \n return {\n optimisticState,\n execute,\n isExecuting,\n hasSucceeded,\n hasErrored,\n result,\n reset\n };\n}","import { useActionState, useEffect, useTransition } from \"react\";\nimport type { UseFormProps, UseFormReturn, Path, FieldValues, DefaultValues } from \"react-hook-form\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { toast } from \"sonner\";\nimport type { z } from \"zod\";\nimport type { ServerActionResponse, ServerActionError } from \"../types\";\n\nexport interface UseFormActionOptions<TFieldValues extends FieldValues, TOutput> {\n // Required - the server action to execute (form action format)\n action: (prevState: ServerActionResponse<TOutput>, formData: FormData) => Promise<ServerActionResponse<TOutput>>;\n \n // Optional Zod schema for validation\n schema?: z.ZodTypeAny;\n \n // React Hook Form options\n defaultValues?: DefaultValues<TFieldValues>;\n mode?: UseFormProps<TFieldValues>[\"mode\"];\n \n // Transform function if you need custom FormData creation\n transformData?: (data: TFieldValues) => FormData;\n \n // Callbacks\n onSuccess?: (data: TOutput) => void | Promise<void>;\n onError?: (error: ServerActionError) => void;\n \n // Behavior options\n resetOnSuccess?: boolean;\n showSuccessToast?: boolean | string | ((data: TOutput) => string);\n showErrorToast?: boolean | string | ((error: ServerActionError) => string);\n}\n\nexport interface UseFormActionReturn<TFieldValues extends FieldValues, TOutput> {\n // React Hook Form instance\n form: UseFormReturn<TFieldValues>;\n \n // Submit handler (pre-bound with handleSubmit)\n onSubmit: (e?: React.BaseSyntheticEvent) => void;\n \n // Loading state (combines isPending and isTransitioning)\n isSubmitting: boolean;\n \n // The current action state\n actionState: ServerActionResponse<TOutput>;\n \n // Utilities\n reset: () => void;\n \n // Raw handlers if needed\n handleSubmit: (data: TFieldValues) => Promise<void>;\n}\n\nfunction isSuccessResponse<T>(response: ServerActionResponse<T>): response is { success: true; data: T } {\n return response.success === true;\n}\n\nfunction objectToFormData(obj: any): FormData {\n const formData = new FormData();\n \n Object.entries(obj).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n if (value instanceof File || value instanceof Blob) {\n formData.append(key, value);\n } else if (Array.isArray(value)) {\n value.forEach((item) => formData.append(`${key}[]`, String(item)));\n } else if (typeof value === \"object\") {\n formData.append(key, JSON.stringify(value));\n } else {\n formData.append(key, String(value));\n }\n }\n });\n \n return formData;\n}\n\nexport function useFormAction<TFieldValues extends FieldValues = FieldValues, TOutput = void>({\n action,\n schema,\n defaultValues,\n mode = \"onChange\",\n transformData,\n onSuccess,\n onError,\n resetOnSuccess = false,\n showSuccessToast = false,\n showErrorToast = true,\n}: UseFormActionOptions<TFieldValues, TOutput>): UseFormActionReturn<TFieldValues, TOutput> {\n // 1. Setup form with React Hook Form\n const form = useForm<TFieldValues>({\n resolver: schema ? (zodResolver as any)(schema) : undefined,\n defaultValues,\n mode,\n } as UseFormProps<TFieldValues>);\n \n // 2. Setup server action state with useActionState\n const initialState: ServerActionResponse<TOutput> = { success: true, data: undefined as TOutput };\n const [actionState, formAction, isPending] = useActionState(action, initialState);\n const [isTransitioning, startTransition] = useTransition();\n \n // 3. Handle server errors and success\n useEffect(() => {\n if (!isSuccessResponse(actionState) && actionState.error) {\n const { error } = actionState;\n \n // Map field errors to form\n if (error.fields) {\n Object.entries(error.fields).forEach(([field, messages]) => {\n if (Array.isArray(messages) && messages.length > 0) {\n form.setError(field as Path<TFieldValues>, {\n type: \"server\",\n message: messages[0],\n });\n }\n });\n }\n \n // Single field error\n if (error.field && error.message && !error.fields) {\n form.setError(error.field as Path<TFieldValues>, {\n type: \"server\",\n message: error.message,\n });\n }\n \n // Global error (no specific field)\n if (error.message && !error.field && !error.fields) {\n if (showErrorToast) {\n const message = typeof showErrorToast === \"function\" \n ? showErrorToast(error)\n : typeof showErrorToast === \"string\"\n ? showErrorToast\n : error.message;\n toast.error(message);\n }\n \n // Also set on root for inline display\n form.setError(\"root\", {\n type: \"server\",\n message: error.message,\n });\n }\n \n // Call error callback\n onError?.(error);\n }\n \n // Handle success\n if (isSuccessResponse(actionState) && actionState.data !== undefined) {\n if (showSuccessToast) {\n const message = typeof showSuccessToast === \"function\"\n ? showSuccessToast(actionState.data)\n : typeof showSuccessToast === \"string\"\n ? showSuccessToast\n : \"Success!\";\n toast.success(message);\n }\n \n if (resetOnSuccess) {\n form.reset();\n }\n \n // Call success callback\n onSuccess?.(actionState.data);\n }\n }, [actionState, form, onError, onSuccess, resetOnSuccess, showErrorToast, showSuccessToast]);\n \n // 4. Submit handler\n const handleSubmit = async (data: TFieldValues): Promise<void> => {\n // Clear any previous errors\n form.clearErrors();\n \n // Transform to FormData\n const formData = transformData \n ? transformData(data)\n : objectToFormData(data);\n \n // Execute with transition for better UX\n startTransition(() => {\n formAction(formData);\n });\n };\n \n // 5. Combined loading state\n const isSubmitting = isPending || isTransitioning;\n \n // 6. Pre-bound submit handler\n const onSubmit = (e?: React.BaseSyntheticEvent): void => {\n e?.preventDefault();\n void form.handleSubmit(handleSubmit)(e!);\n };\n \n return {\n form,\n onSubmit,\n isSubmitting,\n actionState,\n reset: form.reset,\n handleSubmit,\n };\n}"],"mappings":"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","names":["result","useTransition","useTransition","useTransition","toast"]}
1
+ {"version":3,"sources":["../../src/hooks/use-server-action.ts","../../src/hooks/use-optimistic-action.ts","../../src/hooks/use-form-action.ts","../../src/hooks/toast-restorer.tsx"],"sourcesContent":["import { useCallback, useState, useTransition } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { toast } from \"sonner\";\nimport type { ServerAction, ActionResult, ServerActionResponse } from \"../types\";\n\nexport interface UseServerActionOptions<TOutput> {\n onSuccess?: (data: TOutput) => void | Promise<void>;\n onError?: (error: ActionResult<TOutput>) => void | Promise<void>;\n \n successMessage?: string | ((data: TOutput) => string);\n errorMessage?: string | ((error: ActionResult<TOutput>) => string);\n showSuccessToast?: boolean;\n showErrorToast?: boolean;\n \n redirectOnAuthError?: boolean;\n}\n\nexport interface UseServerActionReturn<TInput, TOutput> {\n execute: TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n result: ActionResult<TOutput> | undefined;\n isExecuting: boolean;\n hasSucceeded: boolean;\n hasErrored: boolean;\n \n reset: () => void;\n}\n\nexport function useServerAction<TInput, TOutput>(\n action: ServerAction<TInput, TOutput>,\n options: UseServerActionOptions<TOutput> = {}\n): UseServerActionReturn<TInput, TOutput> {\n const router = useRouter();\n const [result, setResult] = useState<ActionResult<TOutput>>();\n const [isExecuting, setIsExecuting] = useState(false);\n const [, startTransition] = useTransition();\n \n const {\n onSuccess,\n onError,\n successMessage,\n errorMessage,\n showSuccessToast = false,\n showErrorToast = true,\n redirectOnAuthError = true,\n } = options;\n \n const hasSucceeded = !!result?.data;\n const hasErrored = !!result?.serverError || !!result?.validationErrors || !!result?.fetchError;\n \n const reset = useCallback(() => {\n setResult(undefined);\n }, []);\n \n const execute = useCallback(\n async (input?: TInput): Promise<ActionResult<TOutput>> => {\n setIsExecuting(true);\n \n try {\n const response = await (input === undefined \n ? (action as () => Promise<ServerActionResponse<TOutput>>)()\n : (action as (input: TInput) => Promise<ServerActionResponse<TOutput>>)(input));\n \n let actionResult: ActionResult<TOutput>;\n \n if (response.success && response.data !== undefined) {\n actionResult = { data: response.data };\n \n if (showSuccessToast && successMessage) {\n const message = typeof successMessage === \"function\" \n ? successMessage(response.data) \n : successMessage;\n toast.success(message);\n }\n \n await onSuccess?.(response.data);\n \n } else if (!response.success && response.error) {\n actionResult = {\n serverError: response.error,\n validationErrors: response.error.fields,\n };\n \n // Debug logging\n if (process.env.NODE_ENV === 'development') {\n console.log('[useServerAction] Error response:', response.error);\n console.log('[useServerAction] Should redirect?', response.error.shouldRedirect);\n console.log('[useServerAction] Redirect to:', response.error.redirectTo);\n }\n \n if (redirectOnAuthError && response.error.shouldRedirect) {\n // Handle toast before redirect\n if (showErrorToast) {\n const message = errorMessage \n ? (typeof errorMessage === \"function\" \n ? errorMessage(actionResult) \n : errorMessage)\n : response.error.message || \"An error occurred\";\n \n // Always save to localStorage for redirect cases\n // This ensures compatibility with both current and future sonner versions\n if (typeof window !== 'undefined') {\n try {\n const storageKey = 'sonner-toasts';\n const existingToasts = JSON.parse(\n localStorage.getItem(storageKey) || '[]'\n );\n \n // Add our toast in the same format as the PR\n const persistentToast = {\n id: Date.now(),\n type: 'error',\n message,\n persistent: true,\n createdAt: Date.now()\n };\n \n existingToasts.push(persistentToast);\n localStorage.setItem(storageKey, JSON.stringify(existingToasts));\n } catch (err) {\n console.error('Failed to persist toast:', err);\n }\n }\n \n // Always show the toast (with persistent if supported)\n (toast.error as any)(message, { persistent: true });\n }\n \n router.push(response.error.redirectTo || \"/login\");\n return actionResult;\n }\n \n if (showErrorToast) {\n const message = errorMessage \n ? (typeof errorMessage === \"function\" \n ? errorMessage(actionResult) \n : errorMessage)\n : response.error.message || \"An error occurred\";\n toast.error(message);\n }\n \n await onError?.(actionResult);\n } else {\n actionResult = { fetchError: \"Unexpected response format\" };\n }\n \n setResult(actionResult);\n return actionResult;\n \n } catch (error) {\n const fetchError = error instanceof Error ? error.message : \"Network error\";\n const actionResult: ActionResult<TOutput> = { fetchError };\n \n setResult(actionResult);\n \n if (showErrorToast) {\n toast.error(fetchError);\n }\n \n await onError?.(actionResult);\n return actionResult;\n } finally {\n setIsExecuting(false);\n }\n },\n [action, router, showSuccessToast, successMessage, showErrorToast, errorMessage, redirectOnAuthError, onSuccess, onError]\n );\n \n const executeWithTransition = useCallback(\n (input?: TInput): Promise<ActionResult<TOutput>> => {\n return new Promise((resolve) => {\n startTransition(async () => {\n const result = await execute(input);\n resolve(result);\n });\n });\n },\n [execute]\n ) as any;\n \n return {\n execute: executeWithTransition,\n result,\n isExecuting,\n hasSucceeded,\n hasErrored,\n reset,\n };\n}","import { useOptimistic, useTransition } from \"react\";\nimport { useServerAction, type UseServerActionOptions } from \"./use-server-action\";\nimport type { ServerAction, ActionResult } from \"../types\";\n\nexport interface UseOptimisticActionOptions<TInput, TOutput, TOptimistic> extends UseServerActionOptions<TOutput> {\n /**\n * Function to update the optimistic state\n */\n updateFn: TInput extends void \n ? (current: TOptimistic) => TOptimistic\n : (current: TOptimistic, input: TInput) => TOptimistic;\n}\n\nexport interface UseOptimisticActionReturn<TInput, TOutput, TOptimistic> {\n /**\n * The optimistic state\n */\n optimisticState: TOptimistic;\n \n /**\n * Execute the action with optimistic update\n */\n execute: TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n /**\n * Loading state\n */\n isExecuting: boolean;\n \n /**\n * Success state\n */\n hasSucceeded: boolean;\n \n /**\n * Error state\n */\n hasErrored: boolean;\n \n /**\n * Last result\n */\n result: ActionResult<TOutput> | undefined;\n \n /**\n * Reset state\n */\n reset: () => void;\n}\n\n/**\n * Hook for server actions with optimistic updates\n * \n * @example\n * ```typescript\n * const { optimisticState, execute } = useOptimisticAction(\n * currentTodos,\n * addTodoAction,\n * {\n * updateFn: (todos, newTodo) => [...todos, newTodo],\n * onSuccess: (savedTodo) => {\n * // Update with server response if needed\n * }\n * }\n * );\n * ```\n */\nexport function useOptimisticAction<TInput, TOutput, TOptimistic>(\n initialState: TOptimistic,\n action: ServerAction<TInput, TOutput>,\n options: UseOptimisticActionOptions<TInput, TOutput, TOptimistic>\n): UseOptimisticActionReturn<TInput, TOutput, TOptimistic> {\n const [optimisticState, addOptimisticUpdate] = useOptimistic(\n initialState,\n options.updateFn\n );\n \n const [, startTransition] = useTransition();\n \n // Extract updateFn from options to pass the rest to useServerAction\n const { updateFn: _, ...serverActionOptions } = options;\n \n const {\n execute: executeAction,\n isExecuting,\n hasSucceeded,\n hasErrored,\n result,\n reset\n } = useServerAction(action, serverActionOptions);\n \n const execute = (async (input?: TInput): Promise<ActionResult<TOutput>> => {\n // Apply optimistic update\n startTransition(() => {\n if (input !== undefined) {\n addOptimisticUpdate(input);\n } else {\n addOptimisticUpdate(undefined as any);\n }\n });\n \n // Execute the server action\n return executeAction(input as any);\n }) as any;\n \n return {\n optimisticState,\n execute,\n isExecuting,\n hasSucceeded,\n hasErrored,\n result,\n reset\n };\n}","import { useActionState, useEffect, useTransition } from \"react\";\nimport type { UseFormProps, UseFormReturn, Path, FieldValues, DefaultValues } from \"react-hook-form\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { toast } from \"sonner\";\nimport type { z } from \"zod\";\nimport type { ServerActionResponse, ServerActionError } from \"../types\";\n\nexport interface UseFormActionOptions<TFieldValues extends FieldValues, TOutput> {\n // Required - the server action to execute (form action format)\n action: (prevState: ServerActionResponse<TOutput>, formData: FormData) => Promise<ServerActionResponse<TOutput>>;\n \n // Optional Zod schema for validation\n schema?: z.ZodTypeAny;\n \n // React Hook Form options\n defaultValues?: DefaultValues<TFieldValues>;\n mode?: UseFormProps<TFieldValues>[\"mode\"];\n \n // Transform function if you need custom FormData creation\n transformData?: (data: TFieldValues) => FormData;\n \n // Callbacks\n onSuccess?: (data: TOutput) => void | Promise<void>;\n onError?: (error: ServerActionError) => void;\n \n // Behavior options\n resetOnSuccess?: boolean;\n showSuccessToast?: boolean | string | ((data: TOutput) => string);\n showErrorToast?: boolean | string | ((error: ServerActionError) => string);\n}\n\nexport interface UseFormActionReturn<TFieldValues extends FieldValues, TOutput> {\n // React Hook Form instance\n form: UseFormReturn<TFieldValues>;\n \n // Submit handler (pre-bound with handleSubmit)\n onSubmit: (e?: React.BaseSyntheticEvent) => void;\n \n // Loading state (combines isPending and isTransitioning)\n isSubmitting: boolean;\n \n // The current action state\n actionState: ServerActionResponse<TOutput>;\n \n // Utilities\n reset: () => void;\n \n // Raw handlers if needed\n handleSubmit: (data: TFieldValues) => Promise<void>;\n}\n\nfunction isSuccessResponse<T>(response: ServerActionResponse<T>): response is { success: true; data: T } {\n return response.success === true;\n}\n\nfunction objectToFormData(obj: any): FormData {\n const formData = new FormData();\n \n Object.entries(obj).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n if (value instanceof File || value instanceof Blob) {\n formData.append(key, value);\n } else if (Array.isArray(value)) {\n value.forEach((item) => formData.append(`${key}[]`, String(item)));\n } else if (typeof value === \"object\") {\n formData.append(key, JSON.stringify(value));\n } else {\n formData.append(key, String(value));\n }\n }\n });\n \n return formData;\n}\n\nexport function useFormAction<TFieldValues extends FieldValues = FieldValues, TOutput = void>({\n action,\n schema,\n defaultValues,\n mode = \"onChange\",\n transformData,\n onSuccess,\n onError,\n resetOnSuccess = false,\n showSuccessToast = false,\n showErrorToast = true,\n}: UseFormActionOptions<TFieldValues, TOutput>): UseFormActionReturn<TFieldValues, TOutput> {\n // 1. Setup form with React Hook Form\n const form = useForm<TFieldValues>({\n resolver: schema ? (zodResolver as any)(schema) : undefined,\n defaultValues,\n mode,\n } as UseFormProps<TFieldValues>);\n \n // 2. Setup server action state with useActionState\n const initialState: ServerActionResponse<TOutput> = { success: true, data: undefined as TOutput };\n const [actionState, formAction, isPending] = useActionState(action, initialState);\n const [isTransitioning, startTransition] = useTransition();\n \n // 3. Handle server errors and success\n useEffect(() => {\n if (!isSuccessResponse(actionState) && actionState.error) {\n const { error } = actionState;\n \n // Map field errors to form\n if (error.fields) {\n Object.entries(error.fields).forEach(([field, messages]) => {\n if (Array.isArray(messages) && messages.length > 0) {\n form.setError(field as Path<TFieldValues>, {\n type: \"server\",\n message: messages[0],\n });\n }\n });\n }\n \n // Single field error\n if (error.field && error.message && !error.fields) {\n form.setError(error.field as Path<TFieldValues>, {\n type: \"server\",\n message: error.message,\n });\n }\n \n // Global error (no specific field)\n if (error.message && !error.field && !error.fields) {\n if (showErrorToast) {\n const message = typeof showErrorToast === \"function\" \n ? showErrorToast(error)\n : typeof showErrorToast === \"string\"\n ? showErrorToast\n : error.message;\n toast.error(message);\n }\n \n // Also set on root for inline display\n form.setError(\"root\", {\n type: \"server\",\n message: error.message,\n });\n }\n \n // Call error callback\n onError?.(error);\n }\n \n // Handle success\n if (isSuccessResponse(actionState) && actionState.data !== undefined) {\n if (showSuccessToast) {\n const message = typeof showSuccessToast === \"function\"\n ? showSuccessToast(actionState.data)\n : typeof showSuccessToast === \"string\"\n ? showSuccessToast\n : \"Success!\";\n toast.success(message);\n }\n \n if (resetOnSuccess) {\n form.reset();\n }\n \n // Call success callback\n onSuccess?.(actionState.data);\n }\n }, [actionState, form, onError, onSuccess, resetOnSuccess, showErrorToast, showSuccessToast]);\n \n // 4. Submit handler\n const handleSubmit = async (data: TFieldValues): Promise<void> => {\n // Clear any previous errors\n form.clearErrors();\n \n // Transform to FormData\n const formData = transformData \n ? transformData(data)\n : objectToFormData(data);\n \n // Execute with transition for better UX\n startTransition(() => {\n formAction(formData);\n });\n };\n \n // 5. Combined loading state\n const isSubmitting = isPending || isTransitioning;\n \n // 6. Pre-bound submit handler\n const onSubmit = (e?: React.BaseSyntheticEvent): void => {\n e?.preventDefault();\n void form.handleSubmit(handleSubmit)(e!);\n };\n \n return {\n form,\n onSubmit,\n isSubmitting,\n actionState,\n reset: form.reset,\n handleSubmit,\n };\n}","\"use client\";\n\nimport { useEffect } from \"react\";\nimport { toast } from \"sonner\";\n\nexport function ToastRestorer() {\n useEffect(() => {\n // Only run on client side\n if (typeof window === \"undefined\") return;\n \n const storageKey = \"sonner-toasts\";\n \n try {\n const storedToasts = localStorage.getItem(storageKey);\n \n if (storedToasts) {\n const persistentToasts = JSON.parse(storedToasts);\n \n if (Array.isArray(persistentToasts) && persistentToasts.length > 0) {\n // Clear the storage immediately to prevent duplicate toasts\n localStorage.removeItem(storageKey);\n \n // Filter out old toasts (older than 30 seconds)\n const recentToasts = persistentToasts.filter(\n (t: any) => Date.now() - (t.createdAt || 0) < 30000\n );\n \n // Restore toasts with staggered animation like in the PR\n recentToasts.forEach((persistedToast: any, index: number) => {\n // Skip loading toasts as per the PR implementation\n if (persistedToast.type === \"loading\") return;\n \n setTimeout(() => {\n const toastFunction = toast[persistedToast.type as keyof typeof toast];\n \n if (typeof toastFunction === \"function\") {\n // Check if current sonner supports persistent\n if (toastFunction.length >= 2) {\n // Use persistent if available\n (toastFunction as any)(persistedToast.message, { \n persistent: true,\n id: persistedToast.id \n });\n } else {\n // Fallback to normal toast\n toastFunction(persistedToast.message);\n }\n }\n }, index * 150); // Staggered delay like in the PR\n });\n }\n }\n } catch (error) {\n console.error(\"Failed to restore toasts:\", error);\n // Clean up on error\n localStorage.removeItem(storageKey);\n }\n }, []);\n \n return null;\n}"],"mappings":";AAAA,SAAS,aAAa,UAAU,qBAAqB;AACrD,SAAS,iBAAiB;AAC1B,SAAS,aAAa;AA4Bf,SAAS,gBACd,QACA,UAA2C,CAAC,GACJ;AACxC,QAAM,SAAS,UAAU;AACzB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAgC;AAC5D,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,EAAE,eAAe,IAAI,cAAc;AAE1C,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,sBAAsB;AAAA,EACxB,IAAI;AAEJ,QAAM,eAAe,CAAC,CAAC,QAAQ;AAC/B,QAAM,aAAa,CAAC,CAAC,QAAQ,eAAe,CAAC,CAAC,QAAQ,oBAAoB,CAAC,CAAC,QAAQ;AAEpF,QAAM,QAAQ,YAAY,MAAM;AAC9B,cAAU,MAAS;AAAA,EACrB,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU;AAAA,IACd,OAAO,UAAmD;AACxD,qBAAe,IAAI;AAEnB,UAAI;AACF,cAAM,WAAW,OAAO,UAAU,SAC7B,OAAwD,IACxD,OAAqE,KAAK;AAE/E,YAAI;AAEJ,YAAI,SAAS,WAAW,SAAS,SAAS,QAAW;AACnD,yBAAe,EAAE,MAAM,SAAS,KAAK;AAErC,cAAI,oBAAoB,gBAAgB;AACtC,kBAAM,UAAU,OAAO,mBAAmB,aACtC,eAAe,SAAS,IAAI,IAC5B;AACJ,kBAAM,QAAQ,OAAO;AAAA,UACvB;AAEA,gBAAM,YAAY,SAAS,IAAI;AAAA,QAEjC,WAAW,CAAC,SAAS,WAAW,SAAS,OAAO;AAC9C,yBAAe;AAAA,YACb,aAAa,SAAS;AAAA,YACtB,kBAAkB,SAAS,MAAM;AAAA,UACnC;AAGA,cAAI,QAAQ,IAAI,aAAa,eAAe;AAC1C,oBAAQ,IAAI,qCAAqC,SAAS,KAAK;AAC/D,oBAAQ,IAAI,sCAAsC,SAAS,MAAM,cAAc;AAC/E,oBAAQ,IAAI,kCAAkC,SAAS,MAAM,UAAU;AAAA,UACzE;AAEA,cAAI,uBAAuB,SAAS,MAAM,gBAAgB;AAExD,gBAAI,gBAAgB;AAClB,oBAAM,UAAU,eACX,OAAO,iBAAiB,aACrB,aAAa,YAAY,IACzB,eACJ,SAAS,MAAM,WAAW;AAI9B,kBAAI,OAAO,WAAW,aAAa;AACjC,oBAAI;AACF,wBAAM,aAAa;AACnB,wBAAM,iBAAiB,KAAK;AAAA,oBAC1B,aAAa,QAAQ,UAAU,KAAK;AAAA,kBACtC;AAGA,wBAAM,kBAAkB;AAAA,oBACtB,IAAI,KAAK,IAAI;AAAA,oBACb,MAAM;AAAA,oBACN;AAAA,oBACA,YAAY;AAAA,oBACZ,WAAW,KAAK,IAAI;AAAA,kBACtB;AAEA,iCAAe,KAAK,eAAe;AACnC,+BAAa,QAAQ,YAAY,KAAK,UAAU,cAAc,CAAC;AAAA,gBACjE,SAAS,KAAK;AACZ,0BAAQ,MAAM,4BAA4B,GAAG;AAAA,gBAC/C;AAAA,cACF;AAGA,cAAC,MAAM,MAAc,SAAS,EAAE,YAAY,KAAK,CAAC;AAAA,YACpD;AAEA,mBAAO,KAAK,SAAS,MAAM,cAAc,QAAQ;AACjD,mBAAO;AAAA,UACT;AAEA,cAAI,gBAAgB;AAClB,kBAAM,UAAU,eACX,OAAO,iBAAiB,aACrB,aAAa,YAAY,IACzB,eACJ,SAAS,MAAM,WAAW;AAC9B,kBAAM,MAAM,OAAO;AAAA,UACrB;AAEA,gBAAM,UAAU,YAAY;AAAA,QAC9B,OAAO;AACL,yBAAe,EAAE,YAAY,6BAA6B;AAAA,QAC5D;AAEA,kBAAU,YAAY;AACtB,eAAO;AAAA,MAET,SAAS,OAAO;AACd,cAAM,aAAa,iBAAiB,QAAQ,MAAM,UAAU;AAC5D,cAAM,eAAsC,EAAE,WAAW;AAEzD,kBAAU,YAAY;AAEtB,YAAI,gBAAgB;AAClB,gBAAM,MAAM,UAAU;AAAA,QACxB;AAEA,cAAM,UAAU,YAAY;AAC5B,eAAO;AAAA,MACT,UAAE;AACA,uBAAe,KAAK;AAAA,MACtB;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,QAAQ,kBAAkB,gBAAgB,gBAAgB,cAAc,qBAAqB,WAAW,OAAO;AAAA,EAC1H;AAEA,QAAM,wBAAwB;AAAA,IAC5B,CAAC,UAAmD;AAClD,aAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,wBAAgB,YAAY;AAC1B,gBAAMA,UAAS,MAAM,QAAQ,KAAK;AAClC,kBAAQA,OAAM;AAAA,QAChB,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,IACA,CAAC,OAAO;AAAA,EACV;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AC9LA,SAAS,eAAe,iBAAAC,sBAAqB;AAqEtC,SAAS,oBACd,cACA,QACA,SACyD;AACzD,QAAM,CAAC,iBAAiB,mBAAmB,IAAI;AAAA,IAC7C;AAAA,IACA,QAAQ;AAAA,EACV;AAEA,QAAM,CAAC,EAAE,eAAe,IAAIC,eAAc;AAG1C,QAAM,EAAE,UAAU,GAAG,GAAG,oBAAoB,IAAI;AAEhD,QAAM;AAAA,IACJ,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,gBAAgB,QAAQ,mBAAmB;AAE/C,QAAM,UAAW,OAAO,UAAmD;AAEzE,oBAAgB,MAAM;AACpB,UAAI,UAAU,QAAW;AACvB,4BAAoB,KAAK;AAAA,MAC3B,OAAO;AACL,4BAAoB,MAAgB;AAAA,MACtC;AAAA,IACF,CAAC;AAGD,WAAO,cAAc,KAAY;AAAA,EACnC;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACpHA,SAAS,gBAAgB,WAAW,iBAAAC,sBAAqB;AAEzD,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,SAAAC,cAAa;AAgDtB,SAAS,kBAAqB,UAA2E;AACvG,SAAO,SAAS,YAAY;AAC9B;AAEA,SAAS,iBAAiB,KAAoB;AAC5C,QAAM,WAAW,IAAI,SAAS;AAE9B,SAAO,QAAQ,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC5C,QAAI,UAAU,UAAa,UAAU,MAAM;AACzC,UAAI,iBAAiB,QAAQ,iBAAiB,MAAM;AAClD,iBAAS,OAAO,KAAK,KAAK;AAAA,MAC5B,WAAW,MAAM,QAAQ,KAAK,GAAG;AAC/B,cAAM,QAAQ,CAAC,SAAS,SAAS,OAAO,GAAG,GAAG,MAAM,OAAO,IAAI,CAAC,CAAC;AAAA,MACnE,WAAW,OAAO,UAAU,UAAU;AACpC,iBAAS,OAAO,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,MAC5C,OAAO;AACL,iBAAS,OAAO,KAAK,OAAO,KAAK,CAAC;AAAA,MACpC;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAEO,SAAS,cAA8E;AAAA,EAC5F;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,iBAAiB;AACnB,GAA4F;AAE1F,QAAM,OAAO,QAAsB;AAAA,IACjC,UAAU,SAAU,YAAoB,MAAM,IAAI;AAAA,IAClD;AAAA,IACA;AAAA,EACF,CAA+B;AAG/B,QAAM,eAA8C,EAAE,SAAS,MAAM,MAAM,OAAqB;AAChG,QAAM,CAAC,aAAa,YAAY,SAAS,IAAI,eAAe,QAAQ,YAAY;AAChF,QAAM,CAAC,iBAAiB,eAAe,IAAID,eAAc;AAGzD,YAAU,MAAM;AACd,QAAI,CAAC,kBAAkB,WAAW,KAAK,YAAY,OAAO;AACxD,YAAM,EAAE,MAAM,IAAI;AAGlB,UAAI,MAAM,QAAQ;AAChB,eAAO,QAAQ,MAAM,MAAM,EAAE,QAAQ,CAAC,CAAC,OAAO,QAAQ,MAAM;AAC1D,cAAI,MAAM,QAAQ,QAAQ,KAAK,SAAS,SAAS,GAAG;AAClD,iBAAK,SAAS,OAA6B;AAAA,cACzC,MAAM;AAAA,cACN,SAAS,SAAS,CAAC;AAAA,YACrB,CAAC;AAAA,UACH;AAAA,QACF,CAAC;AAAA,MACH;AAGA,UAAI,MAAM,SAAS,MAAM,WAAW,CAAC,MAAM,QAAQ;AACjD,aAAK,SAAS,MAAM,OAA6B;AAAA,UAC/C,MAAM;AAAA,UACN,SAAS,MAAM;AAAA,QACjB,CAAC;AAAA,MACH;AAGA,UAAI,MAAM,WAAW,CAAC,MAAM,SAAS,CAAC,MAAM,QAAQ;AAClD,YAAI,gBAAgB;AAClB,gBAAM,UAAU,OAAO,mBAAmB,aACtC,eAAe,KAAK,IACpB,OAAO,mBAAmB,WAC1B,iBACA,MAAM;AACV,UAAAC,OAAM,MAAM,OAAO;AAAA,QACrB;AAGA,aAAK,SAAS,QAAQ;AAAA,UACpB,MAAM;AAAA,UACN,SAAS,MAAM;AAAA,QACjB,CAAC;AAAA,MACH;AAGA,gBAAU,KAAK;AAAA,IACjB;AAGA,QAAI,kBAAkB,WAAW,KAAK,YAAY,SAAS,QAAW;AACpE,UAAI,kBAAkB;AACpB,cAAM,UAAU,OAAO,qBAAqB,aACxC,iBAAiB,YAAY,IAAI,IACjC,OAAO,qBAAqB,WAC5B,mBACA;AACJ,QAAAA,OAAM,QAAQ,OAAO;AAAA,MACvB;AAEA,UAAI,gBAAgB;AAClB,aAAK,MAAM;AAAA,MACb;AAGA,kBAAY,YAAY,IAAI;AAAA,IAC9B;AAAA,EACF,GAAG,CAAC,aAAa,MAAM,SAAS,WAAW,gBAAgB,gBAAgB,gBAAgB,CAAC;AAG5F,QAAM,eAAe,OAAO,SAAsC;AAEhE,SAAK,YAAY;AAGjB,UAAM,WAAW,gBACb,cAAc,IAAI,IAClB,iBAAiB,IAAI;AAGzB,oBAAgB,MAAM;AACpB,iBAAW,QAAQ;AAAA,IACrB,CAAC;AAAA,EACH;AAGA,QAAM,eAAe,aAAa;AAGlC,QAAM,WAAW,CAAC,MAAuC;AACvD,OAAG,eAAe;AAClB,SAAK,KAAK,aAAa,YAAY,EAAE,CAAE;AAAA,EACzC;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,KAAK;AAAA,IACZ;AAAA,EACF;AACF;;;ACtMA,SAAS,aAAAC,kBAAiB;AAC1B,SAAS,SAAAC,cAAa;AAEf,SAAS,gBAAgB;AAC9B,EAAAD,WAAU,MAAM;AAEd,QAAI,OAAO,WAAW,YAAa;AAEnC,UAAM,aAAa;AAEnB,QAAI;AACF,YAAM,eAAe,aAAa,QAAQ,UAAU;AAEpD,UAAI,cAAc;AAChB,cAAM,mBAAmB,KAAK,MAAM,YAAY;AAEhD,YAAI,MAAM,QAAQ,gBAAgB,KAAK,iBAAiB,SAAS,GAAG;AAElE,uBAAa,WAAW,UAAU;AAGlC,gBAAM,eAAe,iBAAiB;AAAA,YACpC,CAAC,MAAW,KAAK,IAAI,KAAK,EAAE,aAAa,KAAK;AAAA,UAChD;AAGA,uBAAa,QAAQ,CAAC,gBAAqB,UAAkB;AAE3D,gBAAI,eAAe,SAAS,UAAW;AAEvC,uBAAW,MAAM;AACf,oBAAM,gBAAgBC,OAAM,eAAe,IAA0B;AAErE,kBAAI,OAAO,kBAAkB,YAAY;AAEvC,oBAAI,cAAc,UAAU,GAAG;AAE7B,kBAAC,cAAsB,eAAe,SAAS;AAAA,oBAC7C,YAAY;AAAA,oBACZ,IAAI,eAAe;AAAA,kBACrB,CAAC;AAAA,gBACH,OAAO;AAEL,gCAAc,eAAe,OAAO;AAAA,gBACtC;AAAA,cACF;AAAA,YACF,GAAG,QAAQ,GAAG;AAAA,UAChB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,6BAA6B,KAAK;AAEhD,mBAAa,WAAW,UAAU;AAAA,IACpC;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;","names":["result","useTransition","useTransition","useTransition","toast","useEffect","toast"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-action-forge",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A simple, type-safe toolkit for Next.js server actions with Zod validation",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",