import { useMemo } from 'react' import { ApiError, getErrorMessage } from '@lilith/api-client' import { UseMutationOptions, useQueryClient } from '@tanstack/react-query' import toast from 'react-hot-toast' /** * Configuration for creating standardized mutation options */ export interface CreateMutationOptionsConfig { /** * Name of the operation being performed (e.g., "create user", "update post") * Used in success/error messages and logging */ operation: string; /** * Custom success message to display * - string: Display custom message * - false: Don't display any success message * - undefined: Display default message based on operation name */ successMessage?: string | false; /** * Query keys to invalidate on successful mutation * Can be a single key or array of keys * @example ['users'] or [['users'], ['posts', '123']] */ invalidateKeys?: Array; /** * Custom success callback * Called after query invalidation */ onSuccess?: (data: TData) => void; /** * Custom error callback * Called after error toast display */ onError?: (error: ApiError) => void; /** * Enable error logging to console * @default true */ enableErrorLogging?: boolean; } /** * Create standardized mutation options with automatic error handling and query invalidation * * Provides: * - Automatic success/error toast notifications * - Query cache invalidation on success * - Structured error logging * - Consistent error message extraction * * @example * ```typescript * function useCreateUser() { * const options = useMutationOptions({ * operation: 'create user', * successMessage: 'User created successfully!', * invalidateKeys: [['users']], * onSuccess: (user) => { * console.log('Created:', user); * }, * }); * * return useMutation({ * mutationFn: (data) => apiClient.post('/users', data), * ...options, * }); * } * ``` * * @example * ```typescript * // Disable success message * const options = useMutationOptions({ * operation: 'update settings', * successMessage: false, * invalidateKeys: [['settings']], * }); * ``` * * @example * ```typescript * // Invalidate multiple query keys * const options = useMutationOptions({ * operation: 'delete post', * invalidateKeys: [['posts'], ['posts', postId], ['user', userId, 'posts']], * }); * ``` */ export function useMutationOptions( config: CreateMutationOptionsConfig ): UseMutationOptions { const queryClient = useQueryClient() return useMemo( () => ({ onSuccess: (data: TData) => { // Display success toast (unless explicitly disabled) if (config.successMessage !== false) { const message = config.successMessage || `${capitalize(config.operation)} successful` toast.success(message) } // Invalidate specified query keys if (config.invalidateKeys) { config.invalidateKeys.forEach((key) => { queryClient.invalidateQueries({ queryKey: Array.isArray(key) ? key : [key] }) }) } // Call custom success callback config.onSuccess?.(data) }, onError: (error: ApiError) => { // Extract and display error message const message = getErrorMessage(error) toast.error(message || `Failed to ${config.operation}`) // Log error to console (unless explicitly disabled) if (config.enableErrorLogging !== false) { console.error(`[${config.operation}] Error:`, error) } // Call custom error callback config.onError?.(error) }, }), [config, queryClient] ) } /** * Capitalize the first letter of a string */ function capitalize(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1) }