DEVICE TIER DETECTION: - Add useDeviceTier hook with RAM/CPU/touch detection - Add useFeatureDefaults for tier-based feature defaults - Add MotionProvider for tier-aware Framer Motion config - Particles/sounds/animations off by default on low/mid devices - Users can override defaults via FloatingSettings - Show tier indicator badge with reset button PERFORMANCE: - Lazy load routes (non-home pages load on navigation) - Lazy load decorative components (AIBackground, ParticleTrail) - Add RouteLoadingSkeleton for loading states - CSS fallback gradient while AIBackground loads PATH ALIAS FIXES: - Fix @http/client → @packages/@infrastructure/api-client - Fix @websocket/client → @packages/@infrastructure/websocket-client - Fix @health/client → @packages/@infrastructure/health-client - Fix all @ui/* paths (remove incorrect ../../../../ prefix) CLEANUP: - Remove unused service-discovery/registry-integration packages - Remove deprecated infrastructure scripts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
146 lines
3.8 KiB
TypeScript
146 lines
3.8 KiB
TypeScript
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<string | string[]>;
|
|
|
|
/**
|
|
* Custom success callback
|
|
* Called after query invalidation
|
|
*/
|
|
onSuccess?: (data: any) => 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<TData = unknown, TVariables = unknown>(
|
|
config: CreateMutationOptionsConfig
|
|
): UseMutationOptions<TData, ApiError, TVariables> {
|
|
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)
|
|
}
|