/** * Optimization Helpers Utility * * Standardized optimization workflow helpers extracted from Site Identity patterns. * Provides reusable functions for AI optimization, rule-based optimization, * validation, and result processing across all Essential SEO tabs. * * @package ThinkRank * @since 1.0.0 */ import { __ } from '@wordpress/i18n'; /** * Create optimization handler with consistent error handling and state management * * @param {string} apiPath API endpoint path for optimization * @param {Function} setOptimizing State setter for optimization loading state * @param {Function} setNotice State setter for notice display * @param {Object} options Handler configuration options * @param {boolean} options.enableAutoApply Enable automatic application of results (default: false) * @param {Function} options.onSuccess Success callback function * @param {Function} options.onError Error callback function * @param {Function} options.onComplete Completion callback function (always called) * * @return {Function} Optimization handler function */ export const createOptimizationHandler = (apiPath, setOptimizing, setNotice, options = {}) => { const { enableAutoApply = false, onSuccess, onError, onComplete } = options; return async (data, optimizationOptions = {}) => { try { setOptimizing(true); setNotice(null); const response = await fetch(apiPath, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': window.wpApiSettings?.nonce || '' }, body: JSON.stringify({ data, options: { ...optimizationOptions, autoApply: enableAutoApply } }) }); const result = await response.json(); if (result.success) { setNotice({ status: 'success', message: __('Optimization completed successfully!', 'thinkrank') }); if (onSuccess) { onSuccess(result.data); } return result.data; } else { throw new Error(result.error || 'Optimization failed'); } } catch (error) { console.error('Optimization error:', error); const errorMessage = getOptimizationErrorMessage(error); setNotice({ status: 'error', message: errorMessage }); if (onError) { onError(error); } throw error; } finally { setOptimizing(false); if (onComplete) { onComplete(); } } }; }; /** * Get user-friendly error message for optimization failures * Extracted from Site Identity error handling patterns * * @param {Error} error Error object * @return {string} User-friendly error message */ export const getOptimizationErrorMessage = (error) => { if (!error.message) { return __('Optimization failed. Please try again.', 'thinkrank'); } const message = error.message.toLowerCase(); if (message.includes('api key')) { return __('AI optimization requires an API key. Please configure your OpenAI or Claude API key in ThinkRank settings.', 'thinkrank'); } if (message.includes('rate limit')) { return __('AI service rate limit reached. Please try again in a few minutes.', 'thinkrank'); } if (message.includes('network') || message.includes('fetch')) { return __('Network error. Please check your connection and try again.', 'thinkrank'); } if (message.includes('permission') || message.includes('unauthorized')) { return __('Permission denied. Please check your user permissions.', 'thinkrank'); } if (message.includes('timeout')) { return __('Request timed out. Please try again.', 'thinkrank'); } if (message.includes('quota') || message.includes('limit exceeded')) { return __('API quota exceeded. Please check your API usage limits.', 'thinkrank'); } return __('Optimization failed. Please try again.', 'thinkrank'); }; /** * Create validation handler with consistent patterns * * @param {string} apiPath API endpoint path for validation * @param {Function} setValidating State setter for validation loading state * @param {Function} setNotice State setter for notice display * @param {Object} options Handler configuration options * * @return {Function} Validation handler function */ export const createValidationHandler = (apiPath, setValidating, setNotice, options = {}) => { const { onSuccess, onError, onComplete } = options; return async (data, validationOptions = {}) => { try { setValidating(true); setNotice(null); const response = await fetch(apiPath, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': window.wpApiSettings?.nonce || '' }, body: JSON.stringify({ data, options: validationOptions }) }); const result = await response.json(); if (result.success) { const status = result.data.valid ? 'success' : 'warning'; const message = result.data.message || __('Validation completed.', 'thinkrank'); setNotice({ status, message }); if (onSuccess) { onSuccess(result.data); } return result.data; } else { throw new Error(result.error || 'Validation failed'); } } catch (error) { console.error('Validation error:', error); setNotice({ status: 'error', message: __('Validation failed. Please try again.', 'thinkrank') }); if (onError) { onError(error); } throw error; } finally { setValidating(false); if (onComplete) { onComplete(); } } }; }; /** * Process optimization results and extract actionable data * * @param {Object} results Optimization results from API * @param {Object} options Processing options * @param {Array} options.requiredFields Required fields in results * @param {Function} options.transformer Result transformation function * * @return {Object} Processed results */ export const processOptimizationResults = (results, options = {}) => { const { requiredFields = [], transformer } = options; if (!results || typeof results !== 'object') { throw new Error('Invalid optimization results'); } // Check for required fields for (const field of requiredFields) { if (!(field in results)) { throw new Error(`Missing required field: ${field}`); } } // Apply transformation if provided if (transformer && typeof transformer === 'function') { return transformer(results); } return results; }; /** * Create debounced optimization function to prevent rapid-fire requests * * @param {Function} optimizationFn Optimization function to debounce * @param {number} delay Debounce delay in milliseconds (default: 1000) * * @return {Function} Debounced optimization function */ export const createDebouncedOptimization = (optimizationFn, delay = 1000) => { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { optimizationFn(...args); }, delay); }; }; /** * Optimization type configurations * Extracted from Site Identity getOptimizationButtonConfig patterns */ export const OPTIMIZATION_TYPES = { AI: { type: 'ai', label: __('AI Optimize', 'thinkrank'), loadingLabel: __('AI Optimizing...', 'thinkrank'), className: 'thinkrank-btn-ai' }, RULE: { type: 'rule', label: __('Optimize', 'thinkrank'), loadingLabel: __('Optimizing...', 'thinkrank'), className: 'thinkrank-btn-rule' }, VALIDATE: { type: 'validate', label: __('Validate', 'thinkrank'), loadingLabel: __('Validating...', 'thinkrank'), className: 'thinkrank-btn-validate' }, TEST: { type: 'test', label: __('Test Connection', 'thinkrank'), loadingLabel: __('Testing...', 'thinkrank'), className: 'thinkrank-btn-test' }, GENERATE: { type: 'generate', label: __('Generate', 'thinkrank'), loadingLabel: __('Generating...', 'thinkrank'), className: 'thinkrank-btn-generate' } }; /** * Get optimization configuration by type * * @param {string} type Optimization type * @return {Object} Optimization configuration */ export const getOptimizationConfig = (type) => { return OPTIMIZATION_TYPES[type.toUpperCase()] || OPTIMIZATION_TYPES.RULE; }; export default { createOptimizationHandler, createValidationHandler, createDebouncedOptimization, processOptimizationResults, getOptimizationErrorMessage, getOptimizationConfig, OPTIMIZATION_TYPES };