PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.0.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.0.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / src / admin / utils / optimizationHelpers.js

optimizationHelpers.js in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.0.0, at src/admin/utils/optimizationHelpers.js

317 lines 9.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Optimization Helpers Utility
3 *
4 * Standardized optimization workflow helpers extracted from Site Identity patterns.
5 * Provides reusable functions for AI optimization, rule-based optimization,
6 * validation, and result processing across all Essential SEO tabs.
7 *
8 * @package ThinkRank
9 * @since 1.0.0
10 */
11
12 import { __ } from '@wordpress/i18n';
13
14 /**
15 * Create optimization handler with consistent error handling and state management
16 *
17 * @param {string} apiPath API endpoint path for optimization
18 * @param {Function} setOptimizing State setter for optimization loading state
19 * @param {Function} setNotice State setter for notice display
20 * @param {Object} options Handler configuration options
21 * @param {boolean} options.enableAutoApply Enable automatic application of results (default: false)
22 * @param {Function} options.onSuccess Success callback function
23 * @param {Function} options.onError Error callback function
24 * @param {Function} options.onComplete Completion callback function (always called)
25 *
26 * @return {Function} Optimization handler function
27 */
28 export const createOptimizationHandler = (apiPath, setOptimizing, setNotice, options = {}) => {
29 const {
30 enableAutoApply = false,
31 onSuccess,
32 onError,
33 onComplete
34 } = options;
35
36 return async (data, optimizationOptions = {}) => {
37 try {
38 setOptimizing(true);
39 setNotice(null);
40
41 const response = await fetch(apiPath, {
42 method: 'POST',
43 headers: {
44 'Content-Type': 'application/json',
45 'X-WP-Nonce': window.wpApiSettings?.nonce || ''
46 },
47 body: JSON.stringify({
48 data,
49 options: {
50 ...optimizationOptions,
51 autoApply: enableAutoApply
52 }
53 })
54 });
55
56 const result = await response.json();
57
58 if (result.success) {
59 setNotice({
60 status: 'success',
61 message: __('Optimization completed successfully!', 'thinkrank')
62 });
63
64 if (onSuccess) {
65 onSuccess(result.data);
66 }
67
68 return result.data;
69 } else {
70 throw new Error(result.error || 'Optimization failed');
71 }
72 } catch (error) {
73 console.error('Optimization error:', error);
74
75 const errorMessage = getOptimizationErrorMessage(error);
76 setNotice({
77 status: 'error',
78 message: errorMessage
79 });
80
81 if (onError) {
82 onError(error);
83 }
84
85 throw error;
86 } finally {
87 setOptimizing(false);
88
89 if (onComplete) {
90 onComplete();
91 }
92 }
93 };
94 };
95
96 /**
97 * Get user-friendly error message for optimization failures
98 * Extracted from Site Identity error handling patterns
99 *
100 * @param {Error} error Error object
101 * @return {string} User-friendly error message
102 */
103 export const getOptimizationErrorMessage = (error) => {
104 if (!error.message) {
105 return __('Optimization failed. Please try again.', 'thinkrank');
106 }
107
108 const message = error.message.toLowerCase();
109
110 if (message.includes('api key')) {
111 return __('AI optimization requires an API key. Please configure your OpenAI or Claude API key in ThinkRank settings.', 'thinkrank');
112 }
113
114 if (message.includes('rate limit')) {
115 return __('AI service rate limit reached. Please try again in a few minutes.', 'thinkrank');
116 }
117
118 if (message.includes('network') || message.includes('fetch')) {
119 return __('Network error. Please check your connection and try again.', 'thinkrank');
120 }
121
122 if (message.includes('permission') || message.includes('unauthorized')) {
123 return __('Permission denied. Please check your user permissions.', 'thinkrank');
124 }
125
126 if (message.includes('timeout')) {
127 return __('Request timed out. Please try again.', 'thinkrank');
128 }
129
130 if (message.includes('quota') || message.includes('limit exceeded')) {
131 return __('API quota exceeded. Please check your API usage limits.', 'thinkrank');
132 }
133
134 return __('Optimization failed. Please try again.', 'thinkrank');
135 };
136
137 /**
138 * Create validation handler with consistent patterns
139 *
140 * @param {string} apiPath API endpoint path for validation
141 * @param {Function} setValidating State setter for validation loading state
142 * @param {Function} setNotice State setter for notice display
143 * @param {Object} options Handler configuration options
144 *
145 * @return {Function} Validation handler function
146 */
147 export const createValidationHandler = (apiPath, setValidating, setNotice, options = {}) => {
148 const { onSuccess, onError, onComplete } = options;
149
150 return async (data, validationOptions = {}) => {
151 try {
152 setValidating(true);
153 setNotice(null);
154
155 const response = await fetch(apiPath, {
156 method: 'POST',
157 headers: {
158 'Content-Type': 'application/json',
159 'X-WP-Nonce': window.wpApiSettings?.nonce || ''
160 },
161 body: JSON.stringify({
162 data,
163 options: validationOptions
164 })
165 });
166
167 const result = await response.json();
168
169 if (result.success) {
170 const status = result.data.valid ? 'success' : 'warning';
171 const message = result.data.message || __('Validation completed.', 'thinkrank');
172
173 setNotice({
174 status,
175 message
176 });
177
178 if (onSuccess) {
179 onSuccess(result.data);
180 }
181
182 return result.data;
183 } else {
184 throw new Error(result.error || 'Validation failed');
185 }
186 } catch (error) {
187 console.error('Validation error:', error);
188
189 setNotice({
190 status: 'error',
191 message: __('Validation failed. Please try again.', 'thinkrank')
192 });
193
194 if (onError) {
195 onError(error);
196 }
197
198 throw error;
199 } finally {
200 setValidating(false);
201
202 if (onComplete) {
203 onComplete();
204 }
205 }
206 };
207 };
208
209 /**
210 * Process optimization results and extract actionable data
211 *
212 * @param {Object} results Optimization results from API
213 * @param {Object} options Processing options
214 * @param {Array} options.requiredFields Required fields in results
215 * @param {Function} options.transformer Result transformation function
216 *
217 * @return {Object} Processed results
218 */
219 export const processOptimizationResults = (results, options = {}) => {
220 const { requiredFields = [], transformer } = options;
221
222 if (!results || typeof results !== 'object') {
223 throw new Error('Invalid optimization results');
224 }
225
226 // Check for required fields
227 for (const field of requiredFields) {
228 if (!(field in results)) {
229 throw new Error(`Missing required field: ${field}`);
230 }
231 }
232
233 // Apply transformation if provided
234 if (transformer && typeof transformer === 'function') {
235 return transformer(results);
236 }
237
238 return results;
239 };
240
241 /**
242 * Create debounced optimization function to prevent rapid-fire requests
243 *
244 * @param {Function} optimizationFn Optimization function to debounce
245 * @param {number} delay Debounce delay in milliseconds (default: 1000)
246 *
247 * @return {Function} Debounced optimization function
248 */
249 export const createDebouncedOptimization = (optimizationFn, delay = 1000) => {
250 let timeoutId;
251
252 return (...args) => {
253 clearTimeout(timeoutId);
254
255 timeoutId = setTimeout(() => {
256 optimizationFn(...args);
257 }, delay);
258 };
259 };
260
261 /**
262 * Optimization type configurations
263 * Extracted from Site Identity getOptimizationButtonConfig patterns
264 */
265 export const OPTIMIZATION_TYPES = {
266 AI: {
267 type: 'ai',
268 label: __('AI Optimize', 'thinkrank'),
269 loadingLabel: __('AI Optimizing...', 'thinkrank'),
270 className: 'thinkrank-btn-ai'
271 },
272 RULE: {
273 type: 'rule',
274 label: __('Optimize', 'thinkrank'),
275 loadingLabel: __('Optimizing...', 'thinkrank'),
276 className: 'thinkrank-btn-rule'
277 },
278 VALIDATE: {
279 type: 'validate',
280 label: __('Validate', 'thinkrank'),
281 loadingLabel: __('Validating...', 'thinkrank'),
282 className: 'thinkrank-btn-validate'
283 },
284 TEST: {
285 type: 'test',
286 label: __('Test Connection', 'thinkrank'),
287 loadingLabel: __('Testing...', 'thinkrank'),
288 className: 'thinkrank-btn-test'
289 },
290 GENERATE: {
291 type: 'generate',
292 label: __('Generate', 'thinkrank'),
293 loadingLabel: __('Generating...', 'thinkrank'),
294 className: 'thinkrank-btn-generate'
295 }
296 };
297
298 /**
299 * Get optimization configuration by type
300 *
301 * @param {string} type Optimization type
302 * @return {Object} Optimization configuration
303 */
304 export const getOptimizationConfig = (type) => {
305 return OPTIMIZATION_TYPES[type.toUpperCase()] || OPTIMIZATION_TYPES.RULE;
306 };
307
308 export default {
309 createOptimizationHandler,
310 createValidationHandler,
311 createDebouncedOptimization,
312 processOptimizationResults,
313 getOptimizationErrorMessage,
314 getOptimizationConfig,
315 OPTIMIZATION_TYPES
316 };
317