next-action-forge 0.2.7 → 0.2.9

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
@@ -155,6 +155,57 @@ export const updateProfile = authClient
155
155
  });
156
156
  ```
157
157
 
158
+ ### Server-Driven Redirects
159
+
160
+ Define redirects that execute automatically after successful actions:
161
+
162
+ ```typescript
163
+ // Simple redirect
164
+ export const logoutAction = actionClient
165
+ .redirect("/login")
166
+ .action(async () => {
167
+ await clearSession();
168
+ return { message: "Logged out successfully" };
169
+ });
170
+
171
+ // Redirect with configuration
172
+ export const deleteAccountAction = actionClient
173
+ .redirect({
174
+ url: "/goodbye",
175
+ replace: true, // Use router.replace instead of push
176
+ delay: 2000 // Delay redirect by 2 seconds
177
+ })
178
+ .action(async () => {
179
+ await deleteUserAccount();
180
+ return { deleted: true };
181
+ });
182
+
183
+ // Conditional redirect based on result
184
+ export const updateProfileAction = actionClient
185
+ .redirect((result) => result.needsVerification ? "/verify" : "/profile")
186
+ .inputSchema(profileSchema)
187
+ .action(async (input) => {
188
+ const user = await updateUser(input);
189
+ return {
190
+ user,
191
+ needsVerification: !user.emailVerified
192
+ };
193
+ });
194
+ ```
195
+
196
+ Client-side usage:
197
+
198
+ ```tsx
199
+ const { execute } = useServerAction(logoutAction, {
200
+ onSuccess: (data) => {
201
+ // This runs BEFORE the redirect
202
+ toast.success("Logged out successfully");
203
+ },
204
+ preventRedirect: true, // Optionally prevent automatic redirect
205
+ redirectDelay: 1000, // Global delay for all redirects
206
+ });
207
+ ```
208
+
158
209
  ### Form Actions
159
210
 
160
211
  ```typescript
@@ -442,6 +493,7 @@ client
442
493
  .inputSchema(zodSchema) // Set input validation schema
443
494
  .outputSchema(zodSchema) // Set output validation schema
444
495
  .onError(handler) // Set error handler
496
+ .redirect(config) // Set redirect on success
445
497
  .action(serverFunction) // Define the server action
446
498
  .formAction(serverFunction) // Define a form action
447
499
 
@@ -102,8 +102,12 @@ function useServerAction(action, options = {}) {
102
102
  try {
103
103
  const response = await (input === void 0 ? action() : action(input));
104
104
  let actionResult;
105
- if (response.success && response.data !== void 0) {
105
+ if (response.success) {
106
106
  actionResult = { data: response.data };
107
+ if (process.env.NODE_ENV === "development") {
108
+ console.log("[useServerAction] Success response:", response);
109
+ console.log("[useServerAction] Has redirect?", !!response.redirect);
110
+ }
107
111
  if (showSuccessToast && successMessage) {
108
112
  const message = typeof successMessage === "function" ? successMessage(response.data) : successMessage;
109
113
  import_sonner.toast.success(message);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/use-server-action.ts","../../src/next/errors/redirect.ts","../../src/next/errors/http-access-fallback.ts","../../src/next/errors/index.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\";\nimport { isNextNavigationError } from \"../next/errors\";\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 preventRedirect?: boolean;\n redirectDelay?: number;\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\n// Overload for void actions\nexport function useServerAction<TOutput>(\n action: ServerAction<void, TOutput>,\n options?: UseServerActionOptions<TOutput>\n): UseServerActionReturn<void, TOutput>;\n\n// Overload for actions with input\nexport function useServerAction<TInput, TOutput>(\n action: ServerAction<TInput, TOutput>,\n options?: UseServerActionOptions<TOutput>\n): UseServerActionReturn<TInput, TOutput>;\n\n// Implementation\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 preventRedirect = false,\n redirectDelay = 0,\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 // Handle redirect if present in response\n if (response.redirect && !preventRedirect) {\n const redirectConfig = typeof response.redirect === 'string'\n ? { url: response.redirect }\n : response.redirect;\n \n const delay = redirectConfig.delay ?? redirectDelay;\n \n setTimeout(() => {\n if (redirectConfig.replace) {\n router.replace(redirectConfig.url);\n } else {\n router.push(redirectConfig.url);\n }\n }, delay);\n }\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 // Check if this is a Next.js navigation error that should be re-thrown\n if (isNextNavigationError(error)) {\n // Re-throw navigation errors to let Next.js handle them\n throw error;\n }\n \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 (...args: TInput extends void ? [] : [input: TInput]): Promise<ActionResult<TOutput>> => {\n return new Promise((resolve) => {\n startTransition(async () => {\n const result = await execute(args[0] as TInput);\n resolve(result);\n });\n });\n },\n [execute]\n ) as TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n return {\n execute: executeWithTransition,\n result,\n isExecuting,\n hasSucceeded,\n hasErrored,\n reset,\n };\n}\n\n","// Comes from: https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/redirect-error.ts\n\nenum RedirectStatusCode {\n\tSeeOther = 303,\n\tTemporaryRedirect = 307,\n\tPermanentRedirect = 308,\n}\n\nconst REDIRECT_ERROR_CODE = \"NEXT_REDIRECT\";\n\nenum RedirectType {\n\tpush = \"push\",\n\treplace = \"replace\",\n}\n\nexport type RedirectError = Error & {\n\tdigest: `${typeof REDIRECT_ERROR_CODE};${RedirectType};${string};${RedirectStatusCode};`;\n};\n\n/**\n * Checks an error to determine if it's an error generated by the\n * `redirect(url)` helper.\n *\n * @param error the error that may reference a redirect error\n * @returns true if the error is a redirect error\n */\nexport function isRedirectError(error: unknown): error is RedirectError {\n\tif (typeof error !== \"object\" || error === null || !(\"digest\" in error) || typeof error.digest !== \"string\") {\n\t\treturn false;\n\t}\n\n\tconst digest = error.digest.split(\";\");\n\tconst [errorCode, type] = digest;\n\tconst destination = digest.slice(2, -2).join(\";\");\n\tconst status = digest.at(-2);\n\n\tconst statusCode = Number(status);\n\n\treturn (\n\t\terrorCode === REDIRECT_ERROR_CODE &&\n\t\t(type === \"replace\" || type === \"push\") &&\n\t\ttypeof destination === \"string\" &&\n\t\t!isNaN(statusCode) &&\n\t\tstatusCode in RedirectStatusCode\n\t);\n}","// Comes from https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/http-access-fallback/http-access-fallback.ts\n\nconst HTTPAccessErrorStatus = {\n\tNOT_FOUND: 404,\n\tFORBIDDEN: 403,\n\tUNAUTHORIZED: 401,\n};\n\nconst ALLOWED_CODES = new Set(Object.values(HTTPAccessErrorStatus));\n\nconst HTTP_ERROR_FALLBACK_ERROR_CODE = \"NEXT_HTTP_ERROR_FALLBACK\";\n\nexport type HTTPAccessFallbackError = Error & {\n\tdigest: `${typeof HTTP_ERROR_FALLBACK_ERROR_CODE};${string}`;\n};\n\n/**\n * Checks an error to determine if it's an error generated by\n * the HTTP navigation APIs `notFound()`, `forbidden()` or `unauthorized()`.\n *\n * @param error the error that may reference a HTTP access error\n * @returns true if the error is a HTTP access error\n */\nexport function isHTTPAccessFallbackError(error: unknown): error is HTTPAccessFallbackError {\n\tif (typeof error !== \"object\" || error === null || !(\"digest\" in error) || typeof error.digest !== \"string\") {\n\t\treturn false;\n\t}\n\tconst [prefix, httpStatus] = error.digest.split(\";\");\n\n\treturn prefix === HTTP_ERROR_FALLBACK_ERROR_CODE && ALLOWED_CODES.has(Number(httpStatus));\n}\n\nexport function getAccessFallbackHTTPStatus(error: HTTPAccessFallbackError): number {\n\tconst httpStatus = error.digest.split(\";\")[1];\n\treturn Number(httpStatus);\n}","import { isRedirectError } from \"./redirect\";\nimport { isHTTPAccessFallbackError, getAccessFallbackHTTPStatus } from \"./http-access-fallback\";\n\nexport { isRedirectError } from \"./redirect\";\nexport type { RedirectError } from \"./redirect\";\n\nexport { isHTTPAccessFallbackError, getAccessFallbackHTTPStatus } from \"./http-access-fallback\";\nexport type { HTTPAccessFallbackError } from \"./http-access-fallback\";\n\n/**\n * Checks if the error is a navigation error that should be re-thrown\n * This includes redirect errors and HTTP access errors (notFound, forbidden, unauthorized)\n */\nexport function isNextNavigationError(error: unknown): boolean {\n\treturn isRedirectError(error) || isHTTPAccessFallbackError(error);\n}\n\n/**\n * Checks if the error is a notFound error\n * Note: Next.js implements notFound() using HTTP_ERROR_FALLBACK with status 404,\n * not as a separate error type like NEXT_REDIRECT\n */\nexport function isNotFoundError(error: unknown): boolean {\n\treturn isHTTPAccessFallbackError(error) && getAccessFallbackHTTPStatus(error as any) === 404;\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":["RedirectStatusCode","result","import_react","import_react","import_sonner","import_react","import_sonner"]}
1
+ {"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/use-server-action.ts","../../src/next/errors/redirect.ts","../../src/next/errors/http-access-fallback.ts","../../src/next/errors/index.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\";\nimport { isNextNavigationError } from \"../next/errors\";\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 preventRedirect?: boolean;\n redirectDelay?: number;\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\n// Overload for void actions\nexport function useServerAction<TOutput>(\n action: ServerAction<void, TOutput>,\n options?: UseServerActionOptions<TOutput>\n): UseServerActionReturn<void, TOutput>;\n\n// Overload for actions with input\nexport function useServerAction<TInput, TOutput>(\n action: ServerAction<TInput, TOutput>,\n options?: UseServerActionOptions<TOutput>\n): UseServerActionReturn<TInput, TOutput>;\n\n// Implementation\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 preventRedirect = false,\n redirectDelay = 0,\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) {\n actionResult = { data: response.data };\n \n if (process.env.NODE_ENV === 'development') {\n console.log('[useServerAction] Success response:', response);\n console.log('[useServerAction] Has redirect?', !!response.redirect);\n }\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 // Handle redirect if present in response\n if (response.redirect && !preventRedirect) {\n const redirectConfig = typeof response.redirect === 'string'\n ? { url: response.redirect }\n : response.redirect;\n \n const delay = redirectConfig.delay ?? redirectDelay;\n \n setTimeout(() => {\n if (redirectConfig.replace) {\n router.replace(redirectConfig.url);\n } else {\n router.push(redirectConfig.url);\n }\n }, delay);\n }\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 // Check if this is a Next.js navigation error that should be re-thrown\n if (isNextNavigationError(error)) {\n // Re-throw navigation errors to let Next.js handle them\n throw error;\n }\n \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 (...args: TInput extends void ? [] : [input: TInput]): Promise<ActionResult<TOutput>> => {\n return new Promise((resolve) => {\n startTransition(async () => {\n const result = await execute(args[0] as TInput);\n resolve(result);\n });\n });\n },\n [execute]\n ) as TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n return {\n execute: executeWithTransition,\n result,\n isExecuting,\n hasSucceeded,\n hasErrored,\n reset,\n };\n}\n\n","// Comes from: https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/redirect-error.ts\n\nenum RedirectStatusCode {\n\tSeeOther = 303,\n\tTemporaryRedirect = 307,\n\tPermanentRedirect = 308,\n}\n\nconst REDIRECT_ERROR_CODE = \"NEXT_REDIRECT\";\n\nenum RedirectType {\n\tpush = \"push\",\n\treplace = \"replace\",\n}\n\nexport type RedirectError = Error & {\n\tdigest: `${typeof REDIRECT_ERROR_CODE};${RedirectType};${string};${RedirectStatusCode};`;\n};\n\n/**\n * Checks an error to determine if it's an error generated by the\n * `redirect(url)` helper.\n *\n * @param error the error that may reference a redirect error\n * @returns true if the error is a redirect error\n */\nexport function isRedirectError(error: unknown): error is RedirectError {\n\tif (typeof error !== \"object\" || error === null || !(\"digest\" in error) || typeof error.digest !== \"string\") {\n\t\treturn false;\n\t}\n\n\tconst digest = error.digest.split(\";\");\n\tconst [errorCode, type] = digest;\n\tconst destination = digest.slice(2, -2).join(\";\");\n\tconst status = digest.at(-2);\n\n\tconst statusCode = Number(status);\n\n\treturn (\n\t\terrorCode === REDIRECT_ERROR_CODE &&\n\t\t(type === \"replace\" || type === \"push\") &&\n\t\ttypeof destination === \"string\" &&\n\t\t!isNaN(statusCode) &&\n\t\tstatusCode in RedirectStatusCode\n\t);\n}","// Comes from https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/http-access-fallback/http-access-fallback.ts\n\nconst HTTPAccessErrorStatus = {\n\tNOT_FOUND: 404,\n\tFORBIDDEN: 403,\n\tUNAUTHORIZED: 401,\n};\n\nconst ALLOWED_CODES = new Set(Object.values(HTTPAccessErrorStatus));\n\nconst HTTP_ERROR_FALLBACK_ERROR_CODE = \"NEXT_HTTP_ERROR_FALLBACK\";\n\nexport type HTTPAccessFallbackError = Error & {\n\tdigest: `${typeof HTTP_ERROR_FALLBACK_ERROR_CODE};${string}`;\n};\n\n/**\n * Checks an error to determine if it's an error generated by\n * the HTTP navigation APIs `notFound()`, `forbidden()` or `unauthorized()`.\n *\n * @param error the error that may reference a HTTP access error\n * @returns true if the error is a HTTP access error\n */\nexport function isHTTPAccessFallbackError(error: unknown): error is HTTPAccessFallbackError {\n\tif (typeof error !== \"object\" || error === null || !(\"digest\" in error) || typeof error.digest !== \"string\") {\n\t\treturn false;\n\t}\n\tconst [prefix, httpStatus] = error.digest.split(\";\");\n\n\treturn prefix === HTTP_ERROR_FALLBACK_ERROR_CODE && ALLOWED_CODES.has(Number(httpStatus));\n}\n\nexport function getAccessFallbackHTTPStatus(error: HTTPAccessFallbackError): number {\n\tconst httpStatus = error.digest.split(\";\")[1];\n\treturn Number(httpStatus);\n}","import { isRedirectError } from \"./redirect\";\nimport { isHTTPAccessFallbackError, getAccessFallbackHTTPStatus } from \"./http-access-fallback\";\n\nexport { isRedirectError } from \"./redirect\";\nexport type { RedirectError } from \"./redirect\";\n\nexport { isHTTPAccessFallbackError, getAccessFallbackHTTPStatus } from \"./http-access-fallback\";\nexport type { HTTPAccessFallbackError } from \"./http-access-fallback\";\n\n/**\n * Checks if the error is a navigation error that should be re-thrown\n * This includes redirect errors and HTTP access errors (notFound, forbidden, unauthorized)\n */\nexport function isNextNavigationError(error: unknown): boolean {\n\treturn isRedirectError(error) || isHTTPAccessFallbackError(error);\n}\n\n/**\n * Checks if the error is a notFound error\n * Note: Next.js implements notFound() using HTTP_ERROR_FALLBACK with status 404,\n * not as a separate error type like NEXT_REDIRECT\n */\nexport function isNotFoundError(error: unknown): boolean {\n\treturn isHTTPAccessFallbackError(error) && getAccessFallbackHTTPStatus(error as any) === 404;\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":["RedirectStatusCode","result","import_react","import_react","import_sonner","import_react","import_sonner"]}
@@ -73,8 +73,12 @@ function useServerAction(action, options = {}) {
73
73
  try {
74
74
  const response = await (input === void 0 ? action() : action(input));
75
75
  let actionResult;
76
- if (response.success && response.data !== void 0) {
76
+ if (response.success) {
77
77
  actionResult = { data: response.data };
78
+ if (process.env.NODE_ENV === "development") {
79
+ console.log("[useServerAction] Success response:", response);
80
+ console.log("[useServerAction] Has redirect?", !!response.redirect);
81
+ }
78
82
  if (showSuccessToast && successMessage) {
79
83
  const message = typeof successMessage === "function" ? successMessage(response.data) : successMessage;
80
84
  toast.success(message);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/use-server-action.ts","../../src/next/errors/redirect.ts","../../src/next/errors/http-access-fallback.ts","../../src/next/errors/index.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\";\nimport { isNextNavigationError } from \"../next/errors\";\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 preventRedirect?: boolean;\n redirectDelay?: number;\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\n// Overload for void actions\nexport function useServerAction<TOutput>(\n action: ServerAction<void, TOutput>,\n options?: UseServerActionOptions<TOutput>\n): UseServerActionReturn<void, TOutput>;\n\n// Overload for actions with input\nexport function useServerAction<TInput, TOutput>(\n action: ServerAction<TInput, TOutput>,\n options?: UseServerActionOptions<TOutput>\n): UseServerActionReturn<TInput, TOutput>;\n\n// Implementation\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 preventRedirect = false,\n redirectDelay = 0,\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 // Handle redirect if present in response\n if (response.redirect && !preventRedirect) {\n const redirectConfig = typeof response.redirect === 'string'\n ? { url: response.redirect }\n : response.redirect;\n \n const delay = redirectConfig.delay ?? redirectDelay;\n \n setTimeout(() => {\n if (redirectConfig.replace) {\n router.replace(redirectConfig.url);\n } else {\n router.push(redirectConfig.url);\n }\n }, delay);\n }\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 // Check if this is a Next.js navigation error that should be re-thrown\n if (isNextNavigationError(error)) {\n // Re-throw navigation errors to let Next.js handle them\n throw error;\n }\n \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 (...args: TInput extends void ? [] : [input: TInput]): Promise<ActionResult<TOutput>> => {\n return new Promise((resolve) => {\n startTransition(async () => {\n const result = await execute(args[0] as TInput);\n resolve(result);\n });\n });\n },\n [execute]\n ) as TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n return {\n execute: executeWithTransition,\n result,\n isExecuting,\n hasSucceeded,\n hasErrored,\n reset,\n };\n}\n\n","// Comes from: https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/redirect-error.ts\n\nenum RedirectStatusCode {\n\tSeeOther = 303,\n\tTemporaryRedirect = 307,\n\tPermanentRedirect = 308,\n}\n\nconst REDIRECT_ERROR_CODE = \"NEXT_REDIRECT\";\n\nenum RedirectType {\n\tpush = \"push\",\n\treplace = \"replace\",\n}\n\nexport type RedirectError = Error & {\n\tdigest: `${typeof REDIRECT_ERROR_CODE};${RedirectType};${string};${RedirectStatusCode};`;\n};\n\n/**\n * Checks an error to determine if it's an error generated by the\n * `redirect(url)` helper.\n *\n * @param error the error that may reference a redirect error\n * @returns true if the error is a redirect error\n */\nexport function isRedirectError(error: unknown): error is RedirectError {\n\tif (typeof error !== \"object\" || error === null || !(\"digest\" in error) || typeof error.digest !== \"string\") {\n\t\treturn false;\n\t}\n\n\tconst digest = error.digest.split(\";\");\n\tconst [errorCode, type] = digest;\n\tconst destination = digest.slice(2, -2).join(\";\");\n\tconst status = digest.at(-2);\n\n\tconst statusCode = Number(status);\n\n\treturn (\n\t\terrorCode === REDIRECT_ERROR_CODE &&\n\t\t(type === \"replace\" || type === \"push\") &&\n\t\ttypeof destination === \"string\" &&\n\t\t!isNaN(statusCode) &&\n\t\tstatusCode in RedirectStatusCode\n\t);\n}","// Comes from https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/http-access-fallback/http-access-fallback.ts\n\nconst HTTPAccessErrorStatus = {\n\tNOT_FOUND: 404,\n\tFORBIDDEN: 403,\n\tUNAUTHORIZED: 401,\n};\n\nconst ALLOWED_CODES = new Set(Object.values(HTTPAccessErrorStatus));\n\nconst HTTP_ERROR_FALLBACK_ERROR_CODE = \"NEXT_HTTP_ERROR_FALLBACK\";\n\nexport type HTTPAccessFallbackError = Error & {\n\tdigest: `${typeof HTTP_ERROR_FALLBACK_ERROR_CODE};${string}`;\n};\n\n/**\n * Checks an error to determine if it's an error generated by\n * the HTTP navigation APIs `notFound()`, `forbidden()` or `unauthorized()`.\n *\n * @param error the error that may reference a HTTP access error\n * @returns true if the error is a HTTP access error\n */\nexport function isHTTPAccessFallbackError(error: unknown): error is HTTPAccessFallbackError {\n\tif (typeof error !== \"object\" || error === null || !(\"digest\" in error) || typeof error.digest !== \"string\") {\n\t\treturn false;\n\t}\n\tconst [prefix, httpStatus] = error.digest.split(\";\");\n\n\treturn prefix === HTTP_ERROR_FALLBACK_ERROR_CODE && ALLOWED_CODES.has(Number(httpStatus));\n}\n\nexport function getAccessFallbackHTTPStatus(error: HTTPAccessFallbackError): number {\n\tconst httpStatus = error.digest.split(\";\")[1];\n\treturn Number(httpStatus);\n}","import { isRedirectError } from \"./redirect\";\nimport { isHTTPAccessFallbackError, getAccessFallbackHTTPStatus } from \"./http-access-fallback\";\n\nexport { isRedirectError } from \"./redirect\";\nexport type { RedirectError } from \"./redirect\";\n\nexport { isHTTPAccessFallbackError, getAccessFallbackHTTPStatus } from \"./http-access-fallback\";\nexport type { HTTPAccessFallbackError } from \"./http-access-fallback\";\n\n/**\n * Checks if the error is a navigation error that should be re-thrown\n * This includes redirect errors and HTTP access errors (notFound, forbidden, unauthorized)\n */\nexport function isNextNavigationError(error: unknown): boolean {\n\treturn isRedirectError(error) || isHTTPAccessFallbackError(error);\n}\n\n/**\n * Checks if the error is a notFound error\n * Note: Next.js implements notFound() using HTTP_ERROR_FALLBACK with status 404,\n * not as a separate error type like NEXT_REDIRECT\n */\nexport function isNotFoundError(error: unknown): boolean {\n\treturn isHTTPAccessFallbackError(error) && getAccessFallbackHTTPStatus(error as any) === 404;\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":["RedirectStatusCode","result","useTransition","useTransition","useTransition","toast","useEffect","toast"]}
1
+ {"version":3,"sources":["../../src/hooks/use-server-action.ts","../../src/next/errors/redirect.ts","../../src/next/errors/http-access-fallback.ts","../../src/next/errors/index.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\";\nimport { isNextNavigationError } from \"../next/errors\";\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 preventRedirect?: boolean;\n redirectDelay?: number;\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\n// Overload for void actions\nexport function useServerAction<TOutput>(\n action: ServerAction<void, TOutput>,\n options?: UseServerActionOptions<TOutput>\n): UseServerActionReturn<void, TOutput>;\n\n// Overload for actions with input\nexport function useServerAction<TInput, TOutput>(\n action: ServerAction<TInput, TOutput>,\n options?: UseServerActionOptions<TOutput>\n): UseServerActionReturn<TInput, TOutput>;\n\n// Implementation\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 preventRedirect = false,\n redirectDelay = 0,\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) {\n actionResult = { data: response.data };\n \n if (process.env.NODE_ENV === 'development') {\n console.log('[useServerAction] Success response:', response);\n console.log('[useServerAction] Has redirect?', !!response.redirect);\n }\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 // Handle redirect if present in response\n if (response.redirect && !preventRedirect) {\n const redirectConfig = typeof response.redirect === 'string'\n ? { url: response.redirect }\n : response.redirect;\n \n const delay = redirectConfig.delay ?? redirectDelay;\n \n setTimeout(() => {\n if (redirectConfig.replace) {\n router.replace(redirectConfig.url);\n } else {\n router.push(redirectConfig.url);\n }\n }, delay);\n }\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 // Check if this is a Next.js navigation error that should be re-thrown\n if (isNextNavigationError(error)) {\n // Re-throw navigation errors to let Next.js handle them\n throw error;\n }\n \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 (...args: TInput extends void ? [] : [input: TInput]): Promise<ActionResult<TOutput>> => {\n return new Promise((resolve) => {\n startTransition(async () => {\n const result = await execute(args[0] as TInput);\n resolve(result);\n });\n });\n },\n [execute]\n ) as TInput extends void \n ? () => Promise<ActionResult<TOutput>>\n : (input: TInput) => Promise<ActionResult<TOutput>>;\n \n return {\n execute: executeWithTransition,\n result,\n isExecuting,\n hasSucceeded,\n hasErrored,\n reset,\n };\n}\n\n","// Comes from: https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/redirect-error.ts\n\nenum RedirectStatusCode {\n\tSeeOther = 303,\n\tTemporaryRedirect = 307,\n\tPermanentRedirect = 308,\n}\n\nconst REDIRECT_ERROR_CODE = \"NEXT_REDIRECT\";\n\nenum RedirectType {\n\tpush = \"push\",\n\treplace = \"replace\",\n}\n\nexport type RedirectError = Error & {\n\tdigest: `${typeof REDIRECT_ERROR_CODE};${RedirectType};${string};${RedirectStatusCode};`;\n};\n\n/**\n * Checks an error to determine if it's an error generated by the\n * `redirect(url)` helper.\n *\n * @param error the error that may reference a redirect error\n * @returns true if the error is a redirect error\n */\nexport function isRedirectError(error: unknown): error is RedirectError {\n\tif (typeof error !== \"object\" || error === null || !(\"digest\" in error) || typeof error.digest !== \"string\") {\n\t\treturn false;\n\t}\n\n\tconst digest = error.digest.split(\";\");\n\tconst [errorCode, type] = digest;\n\tconst destination = digest.slice(2, -2).join(\";\");\n\tconst status = digest.at(-2);\n\n\tconst statusCode = Number(status);\n\n\treturn (\n\t\terrorCode === REDIRECT_ERROR_CODE &&\n\t\t(type === \"replace\" || type === \"push\") &&\n\t\ttypeof destination === \"string\" &&\n\t\t!isNaN(statusCode) &&\n\t\tstatusCode in RedirectStatusCode\n\t);\n}","// Comes from https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/http-access-fallback/http-access-fallback.ts\n\nconst HTTPAccessErrorStatus = {\n\tNOT_FOUND: 404,\n\tFORBIDDEN: 403,\n\tUNAUTHORIZED: 401,\n};\n\nconst ALLOWED_CODES = new Set(Object.values(HTTPAccessErrorStatus));\n\nconst HTTP_ERROR_FALLBACK_ERROR_CODE = \"NEXT_HTTP_ERROR_FALLBACK\";\n\nexport type HTTPAccessFallbackError = Error & {\n\tdigest: `${typeof HTTP_ERROR_FALLBACK_ERROR_CODE};${string}`;\n};\n\n/**\n * Checks an error to determine if it's an error generated by\n * the HTTP navigation APIs `notFound()`, `forbidden()` or `unauthorized()`.\n *\n * @param error the error that may reference a HTTP access error\n * @returns true if the error is a HTTP access error\n */\nexport function isHTTPAccessFallbackError(error: unknown): error is HTTPAccessFallbackError {\n\tif (typeof error !== \"object\" || error === null || !(\"digest\" in error) || typeof error.digest !== \"string\") {\n\t\treturn false;\n\t}\n\tconst [prefix, httpStatus] = error.digest.split(\";\");\n\n\treturn prefix === HTTP_ERROR_FALLBACK_ERROR_CODE && ALLOWED_CODES.has(Number(httpStatus));\n}\n\nexport function getAccessFallbackHTTPStatus(error: HTTPAccessFallbackError): number {\n\tconst httpStatus = error.digest.split(\";\")[1];\n\treturn Number(httpStatus);\n}","import { isRedirectError } from \"./redirect\";\nimport { isHTTPAccessFallbackError, getAccessFallbackHTTPStatus } from \"./http-access-fallback\";\n\nexport { isRedirectError } from \"./redirect\";\nexport type { RedirectError } from \"./redirect\";\n\nexport { isHTTPAccessFallbackError, getAccessFallbackHTTPStatus } from \"./http-access-fallback\";\nexport type { HTTPAccessFallbackError } from \"./http-access-fallback\";\n\n/**\n * Checks if the error is a navigation error that should be re-thrown\n * This includes redirect errors and HTTP access errors (notFound, forbidden, unauthorized)\n */\nexport function isNextNavigationError(error: unknown): boolean {\n\treturn isRedirectError(error) || isHTTPAccessFallbackError(error);\n}\n\n/**\n * Checks if the error is a notFound error\n * Note: Next.js implements notFound() using HTTP_ERROR_FALLBACK with status 404,\n * not as a separate error type like NEXT_REDIRECT\n */\nexport function isNotFoundError(error: unknown): boolean {\n\treturn isHTTPAccessFallbackError(error) && getAccessFallbackHTTPStatus(error as any) === 404;\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":["RedirectStatusCode","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.2.7",
3
+ "version": "0.2.9",
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",