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 / components / essential-seo / SiteIdentityTab.js

SiteIdentityTab.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/components/essential-seo/SiteIdentityTab.js

827 lines 33.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Site Identity Tab Component - Complete Implementation
3 *
4 * Full feature parity with PHP implementation including:
5 * - Title format management with templates
6 * - Advanced breadcrumb settings
7 * - Robots.txt management
8 * - Schema & SEO settings
9 * - AI optimization features
10 *
11 * Now works with sidebar navigation instead of internal tabs
12 *
13 * @package ThinkRank
14 * @since 1.0.0
15 */
16
17 import { useState, useEffect } from '@wordpress/element';
18 import { __ } from '@wordpress/i18n';
19 import {
20 Card,
21 CardBody,
22 CardHeader,
23 Spinner,
24 Button,
25 Flex,
26 FlexItem
27 } from '@wordpress/components';
28 import apiFetch from '@wordpress/api-fetch';
29 // Import shared components for refactoring
30 import OptimizationButton from '../common/OptimizationButton';
31 import SettingsCard from '../common/SettingsCard';
32
33 // Import extracted site identity components
34 import SiteInformation from './site-identity/SiteInformation';
35 import TitleFormats from './site-identity/TitleFormats';
36 import BreadcrumbSettings from './site-identity/BreadcrumbSettings';
37 import HeroSection from './site-identity/HeroSection';
38 import LocalSEO from './site-identity/LocalSEO';
39 import SiteIdentityValidation from './site-identity/SiteIdentityValidation';
40 import SiteIdentityOptimization from './site-identity/SiteIdentityOptimization';
41
42 /**
43 * Site Identity Tab Component
44 */
45 const SiteIdentityTab = ({ activeSubSection = 'basic-info' }) => {
46 // State management
47 const [isLoading, setIsLoading] = useState(true);
48 const [isSaving, setIsSaving] = useState(false);
49 const [isValidating, setIsValidating] = useState(false);
50 const [isOptimizing, setIsOptimizing] = useState(false);
51
52 const [settings, setSettings] = useState({});
53 const [hasChanges, setHasChanges] = useState(false);
54 const [notice, setNotice] = useState(null);
55 const [titleTemplates, setTitleTemplates] = useState([]);
56 const [breadcrumbTypes, setBreadcrumbTypes] = useState([]);
57 const [validationResults, setValidationResults] = useState(null);
58 const [optimizationResults, setOptimizationResults] = useState({});
59 const [aiOptimizationResults, setAiOptimizationResults] = useState(null);
60 const [isAiOptimizing, setIsAiOptimizing] = useState(false);
61
62 useEffect(() => {
63 loadSettings();
64 loadTitleTemplates();
65 loadBreadcrumbTypes();
66 }, []);
67
68 // Clear validation results when switching between tabs
69 useEffect(() => {
70 // Clear validation when switching tabs to avoid showing stale results
71 setValidationResults(null);
72 }, [activeSubSection]);
73
74 // Clear notice when component unmounts or tab changes
75 useEffect(() => {
76 return () => {
77 setNotice(null);
78 };
79 }, []);
80
81 /**
82 * Get dynamic tab display name for notices
83 */
84 const getTabDisplayName = () => {
85 return __('Site Identity', 'thinkrank');
86 };
87
88 /**
89 * Get sub-section specific display name for notices
90 */
91 const getSubSectionDisplayName = () => {
92 const subSectionNames = {
93 'basic-info': __('Basic Info', 'thinkrank'),
94 'title-formats': __('Title Formats', 'thinkrank'),
95 'breadcrumbs': __('Breadcrumbs', 'thinkrank'),
96 'hero-section': __('Hero & Branding', 'thinkrank'),
97 'local-seo': __('Business Info', 'thinkrank'),
98 'robots-txt': __('Robots.txt', 'thinkrank')
99 };
100
101 return subSectionNames[activeSubSection] || getTabDisplayName();
102 };
103
104 /**
105 * Get default settings structure (matches PHP implementation)
106 */
107 const getDefaultSettings = () => ({
108 enabled: true,
109 // Title format settings
110 homepage_title: '%site_title% | %site_description%',
111 post_title: '%post_title% | %site_title%',
112 page_title: '%page_title% | %site_title%',
113 category_title: '%category_title% | %site_title%',
114 tag_title: '%tag_title% | %site_title%',
115 author_title: '%author_name% | %site_title%',
116 search_title: 'Search Results for "%search_term%" | %site_title%',
117 archive_title: '%archive_title% | %site_title%',
118 title_separator: 'pipe',
119 // Site information
120 site_name: '',
121 site_description: '',
122 tagline: '',
123 // Breadcrumb settings
124 breadcrumbs_enabled: true,
125 breadcrumb_type: 'hierarchical',
126 breadcrumb_home_text: 'Home',
127 breadcrumb_separator: '',
128 breadcrumb_prefix: '',
129 show_current_page: true,
130
131 // SEO defaults
132 default_meta_description: '',
133 social_media_accounts: [],
134 // Schema settings
135 organization_schema: true,
136 knowledge_graph: true,
137 // Site assets
138 logo_url: '',
139 favicon_url: '',
140 apple_touch_icon_url: ''
141 });
142
143 /**
144 * Load settings from API
145 */
146 const loadSettings = async () => {
147 try {
148 setIsLoading(true);
149 const response = await apiFetch({
150 path: '/thinkrank/v1/site-identity/settings',
151 method: 'GET'
152 });
153
154 if (response.success) {
155 setSettings({ ...getDefaultSettings(), ...response.data.settings });
156 } else {
157 setSettings(getDefaultSettings());
158 }
159 } catch (error) {
160 console.error('Site Identity settings load error:', error);
161 setSettings(getDefaultSettings());
162 } finally {
163 setIsLoading(false);
164 }
165 };
166
167 /**
168 * Load title templates from API
169 */
170 const loadTitleTemplates = async () => {
171 try {
172 const response = await apiFetch({
173 path: '/thinkrank/v1/site-identity/title/templates',
174 method: 'GET'
175 });
176
177 if (response.success) {
178 setTitleTemplates(response.data.templates || []);
179 }
180 } catch (error) {
181 console.error('Failed to load title templates:', error);
182 }
183 };
184
185 /**
186 * Load breadcrumb types from API
187 */
188 const loadBreadcrumbTypes = async () => {
189 try {
190 const response = await apiFetch({
191 path: '/thinkrank/v1/site-identity/breadcrumbs/types',
192 method: 'GET'
193 });
194
195 if (response.success) {
196 setBreadcrumbTypes(response.data.types || []);
197 }
198 } catch (error) {
199 console.error('Failed to load breadcrumb types:', error);
200 }
201 };
202
203 /**
204 * Handle setting changes
205 */
206 const handleSettingChange = (key, value) => {
207 setSettings(prev => ({ ...prev, [key]: value }));
208 setHasChanges(true);
209 };
210
211 /**
212 * Save settings to API
213 */
214 const saveSettings = async () => {
215 try {
216 setIsSaving(true);
217 setNotice(null);
218
219 const response = await apiFetch({
220 path: '/thinkrank/v1/site-identity/settings',
221 method: 'POST',
222 data: {
223 settings,
224 context_type: 'site'
225 }
226 });
227
228 if (response.success) {
229 setHasChanges(false);
230 setNotice({
231 status: 'success',
232 message: __(`${getTabDisplayName()} settings saved successfully!`, 'thinkrank')
233 });
234 } else {
235 throw new Error(response.error || 'Failed to save settings');
236 }
237 } catch (error) {
238 console.error('Site Identity settings save error:', error);
239 setNotice({
240 status: 'error',
241 message: __(`Failed to save ${getTabDisplayName()} settings. Please try again.`, 'thinkrank')
242 });
243 } finally {
244 setIsSaving(false);
245 }
246 };
247
248 /**
249 * Validate settings using API
250 */
251 const validateSettings = async () => {
252 try {
253 setIsValidating(true);
254 const response = await apiFetch({
255 path: '/thinkrank/v1/site-identity/validate',
256 method: 'POST',
257 data: {
258 settings,
259 context_type: 'site',
260 tab_context: activeSubSection
261 }
262 });
263
264 if (response.success) {
265 setValidationResults(response.data);
266
267 const hasErrors = response.data.issues && response.data.issues.length > 0;
268 const hasWarnings = response.data.warnings && response.data.warnings.length > 0;
269
270 let status = 'success';
271 let message = __(`${getSubSectionDisplayName()} validation completed successfully!`, 'thinkrank');
272
273 if (hasErrors) {
274 status = 'error';
275 message = __(`${getSubSectionDisplayName()} validation found errors that need to be fixed.`, 'thinkrank');
276 } else if (hasWarnings) {
277 status = 'warning';
278 message = __(`${getSubSectionDisplayName()} validation completed with warnings.`, 'thinkrank');
279 }
280
281 setNotice({
282 status: status,
283 message: message
284 });
285 }
286 } catch (error) {
287 console.error('Validation error:', error);
288 setNotice({
289 status: 'error',
290 message: __(`Failed to validate ${getSubSectionDisplayName()} settings.`, 'thinkrank')
291 });
292 } finally {
293 setIsValidating(false);
294 }
295 };
296
297 /**
298 * Optimize site identity using rule-based optimization
299 */
300 const optimizeIdentity = async (focus = 'all') => {
301 try {
302 setIsOptimizing(true);
303 const response = await apiFetch({
304 path: '/thinkrank/v1/site-identity/optimize',
305 method: 'POST',
306 data: {
307 identity_data: settings,
308 options: { focus }
309 }
310 });
311
312 if (response.success) {
313 // Store results per tab/section
314 setOptimizationResults(prev => ({
315 ...prev,
316 [focus]: response.data
317 }));
318 setNotice({
319 status: 'success',
320 message: __(`${getSubSectionDisplayName()} optimization completed successfully!`, 'thinkrank')
321 });
322
323 // Scroll to results card
324 scrollToResults();
325 }
326 } catch (error) {
327 console.error('Optimization error:', error);
328 setNotice({
329 status: 'error',
330 message: __(`Failed to optimize ${getSubSectionDisplayName()}. Please try again.`, 'thinkrank')
331 });
332 } finally {
333 setIsOptimizing(false);
334 }
335 };
336
337 /**
338 * Optimize site information using AI
339 */
340 const optimizeBasicInfo = async () => {
341 try {
342 setIsAiOptimizing(true);
343 const response = await apiFetch({
344 path: '/thinkrank/v1/site-identity/ai-optimize-info',
345 method: 'POST',
346 data: {
347 site_data: {
348 site_name: settings.site_name || '',
349 site_description: settings.site_description || '',
350 tagline: settings.tagline || '',
351 default_meta_description: settings.default_meta_description || ''
352 },
353 business_type: 'website',
354 target_audience: 'general',
355 tone: 'professional'
356 }
357 });
358
359 if (response.success) {
360 setAiOptimizationResults(response.data);
361
362 // Show success message with AI insights
363 let message = __(`${getSubSectionDisplayName()} AI optimization completed!`, 'thinkrank');
364 if (response.data.ai_model) {
365 message += ` (${response.data.ai_model})`;
366 }
367 if (response.data.suggestions && response.data.suggestions.length > 0) {
368 message += ` ${response.data.suggestions.length} ${__('suggestions generated.', 'thinkrank')}`;
369 }
370
371 setNotice({
372 status: 'success',
373 message: message
374 });
375
376 // Scroll to results card
377 scrollToResults();
378 }
379 } catch (error) {
380 console.error('AI optimization error:', error);
381
382 // Handle specific AI-related errors
383 let errorMessage = __(`Failed to optimize ${getSubSectionDisplayName()} information.`, 'thinkrank');
384 if (error.message && error.message.includes('API key')) {
385 errorMessage = __('AI optimization requires an API key. Please configure your OpenAI or Claude API key in ThinkRank settings.', 'thinkrank');
386 } else if (error.message && error.message.includes('rate limit')) {
387 errorMessage = __('AI service rate limit reached. Please try again in a few minutes.', 'thinkrank');
388 }
389
390 setNotice({
391 status: 'error',
392 message: errorMessage
393 });
394 } finally {
395 setIsAiOptimizing(false);
396 }
397 };
398
399 /**
400 * Scroll to optimization results card
401 */
402 const scrollToResults = () => {
403 // Small delay to ensure the results card is rendered
404 setTimeout(() => {
405 const resultsCard = document.querySelector('.site-identity-optimization-results');
406 if (resultsCard) {
407 resultsCard.scrollIntoView({
408 behavior: 'smooth',
409 block: 'start',
410 inline: 'nearest'
411 });
412 }
413 }, 100);
414 };
415
416 /**
417 * Optimize hero content using AI
418 */
419 const optimizeHeroContent = async () => {
420 try {
421 setIsOptimizing(true);
422 const response = await apiFetch({
423 path: '/thinkrank/v1/site-identity/ai-optimize-hero',
424 method: 'POST',
425 data: {
426 hero_data: {
427 hero_title: settings.hero_title || '',
428 hero_subtitle: settings.hero_subtitle || '',
429 hero_cta_text: settings.hero_cta_text || '',
430 hero_cta_url: settings.hero_cta_url || ''
431 },
432 context: {
433 site_name: settings.site_name || document.title.replace('', ' - ').split(' - ')[0] || 'Your Website',
434 site_url: window.location.origin,
435 business_type: settings.business_type || 'website',
436 site_description: settings.site_description || ''
437 },
438 business_type: settings.business_type || 'website',
439 target_audience: 'general',
440 tone: 'professional'
441 }
442 });
443
444 if (response.success) {
445 // Store results for optimization display
446 setOptimizationResults(prev => ({
447 ...prev,
448 hero_section: response.data
449 }));
450
451 // Apply optimized data to settings
452 const optimizedData = response.data.optimized_data;
453 if (optimizedData) {
454 Object.keys(optimizedData).forEach(key => {
455 if (optimizedData[key]) {
456 handleSettingChange(key, optimizedData[key]);
457 }
458 });
459 }
460
461 // Show success message with AI insights
462 let message = __(`${getSubSectionDisplayName()} AI optimization completed!`, 'thinkrank');
463 if (response.data.ai_model) {
464 message += ` (${response.data.ai_model})`;
465 }
466 if (response.data.suggestions && response.data.suggestions.length > 0) {
467 message += ` ${response.data.suggestions.length} ${__('suggestions generated.', 'thinkrank')}`;
468 }
469
470 setNotice({
471 status: 'success',
472 message: message
473 });
474
475 // Scroll to results card
476 scrollToResults();
477 }
478 } catch (error) {
479 console.error('Hero AI optimization error:', error);
480
481 // Handle specific AI-related errors
482 let errorMessage = __(`Failed to optimize ${getSubSectionDisplayName()} content.`, 'thinkrank');
483 if (error.message && error.message.includes('API key')) {
484 errorMessage = __('AI optimization requires an API key. Please configure your OpenAI or Claude API key in ThinkRank settings.', 'thinkrank');
485 } else if (error.message && error.message.includes('rate limit')) {
486 errorMessage = __('AI service rate limit reached. Please try again in a few minutes.', 'thinkrank');
487 }
488
489 setNotice({
490 status: 'error',
491 message: errorMessage
492 });
493 } finally {
494 setIsOptimizing(false);
495 }
496 };
497
498 /**
499 * Determine optimization button type based on active section
500 */
501 const getOptimizationButtonConfig = () => {
502 switch (activeSubSection) {
503 case 'basic-info':
504 return {
505 type: 'ai',
506 label: __('AI Optimize', 'thinkrank'),
507 loadingLabel: __('AI Optimizing...', 'thinkrank'),
508 onClick: optimizeBasicInfo,
509 isBusy: isAiOptimizing,
510 disabled: !settings.enabled || isAiOptimizing
511 };
512 case 'title-formats':
513 return {
514 type: 'rule',
515 label: __('Validate & Optimize Titles', 'thinkrank'),
516 loadingLabel: __('Optimizing...', 'thinkrank'),
517 onClick: () => optimizeIdentity('title_formats'),
518 isBusy: isOptimizing,
519 disabled: !settings.enabled || isOptimizing
520 };
521 case 'breadcrumbs':
522 return {
523 type: 'rule',
524 label: __('Validate & Optimize Breadcrumbs', 'thinkrank'),
525 loadingLabel: __('Optimizing...', 'thinkrank'),
526 onClick: () => optimizeIdentity('breadcrumbs'),
527 isBusy: isOptimizing,
528 disabled: !settings.enabled || isOptimizing
529 };
530 case 'hero-section':
531 return {
532 type: 'ai',
533 label: __('AI Optimize Hero', 'thinkrank'),
534 loadingLabel: __('AI Optimizing...', 'thinkrank'),
535 onClick: optimizeHeroContent,
536 isBusy: isOptimizing,
537 disabled: !settings.enabled || isOptimizing
538 };
539 case 'local-seo':
540 return {
541 type: 'validate',
542 label: __('Validate Business Info', 'thinkrank'),
543 loadingLabel: __('Validating...', 'thinkrank'),
544 onClick: validateSettings,
545 isBusy: isValidating,
546 disabled: !settings.enabled || isValidating
547 };
548 default:
549 return {
550 type: 'rule',
551 label: __('Validate & Optimize', 'thinkrank'),
552 loadingLabel: __('Optimizing...', 'thinkrank'),
553 onClick: () => optimizeIdentity('all'),
554 isBusy: isOptimizing,
555 disabled: !settings.enabled || isOptimizing
556 };
557 }
558 };
559
560
561
562
563
564 if (isLoading) {
565 return (
566 <div className="thinkrank-loading">
567 <Spinner />
568 <p>{__('Loading site identity settings...', 'thinkrank')}</p>
569 </div>
570 );
571 }
572
573 return (
574 <div className="thinkrank-site-identity-tab">
575
576
577 <SettingsCard
578 title={__('Site Identity Settings', 'thinkrank')}
579 enabled={settings.enabled}
580 onToggle={(value) => handleSettingChange('enabled', value)}
581 toggleLabel={__('Enable Site Identity', 'thinkrank')}
582 optimizationButtons={[
583 // Show Validate button for tabs with form inputs
584 ['basic-info', 'title-formats', 'breadcrumbs', 'hero-section', 'local-seo'].includes(activeSubSection) && (
585 <OptimizationButton
586 key="validate"
587 type="validate"
588 onClick={validateSettings}
589 isBusy={isValidating}
590 disabled={!settings.enabled || isValidating}
591 />
592 ),
593 // Main optimization button
594 (() => {
595 const buttonConfig = getOptimizationButtonConfig();
596 return (
597 <OptimizationButton
598 key="optimize"
599 type={buttonConfig.type}
600 label={buttonConfig.label}
601 loadingLabel={buttonConfig.loadingLabel}
602 onClick={buttonConfig.onClick}
603 isBusy={buttonConfig.isBusy}
604 disabled={buttonConfig.disabled}
605 />
606 );
607 })()
608 ].filter(Boolean)}
609 notice={notice}
610 onNoticeRemove={() => setNotice(null)}
611 showDisabledWarning={true}
612 disabledWarningMessage={__('Site identity features are disabled. Enable to configure title formats, breadcrumbs, and robots.txt.', 'thinkrank')}
613 >
614
615 {/* Content based on active subsection */}
616 {(() => {
617 // Map activeSubSection to tab names for compatibility
618 const sectionMap = {
619 'basic-info': 'basic',
620 'title-formats': 'titles',
621 'breadcrumbs': 'breadcrumbs',
622 'hero-section': 'hero',
623 'local-seo': 'local',
624 'robots-txt': 'robots'
625 };
626
627 const tabName = sectionMap[activeSubSection] || 'basic';
628
629 switch (tabName) {
630 case 'basic':
631 return (
632 <SiteInformation
633 settings={settings}
634 handleSettingChange={handleSettingChange}
635 />
636 );
637
638 case 'titles':
639 return (
640 <TitleFormats
641 settings={settings}
642 handleSettingChange={handleSettingChange}
643 />
644 );
645
646 case 'breadcrumbs':
647 return (
648 <BreadcrumbSettings
649 settings={settings}
650 handleSettingChange={handleSettingChange}
651 />
652 );
653
654 case 'hero':
655 return (
656 <HeroSection
657 settings={settings}
658 handleSettingChange={handleSettingChange}
659 />
660 );
661
662 case 'local':
663 return (
664 <LocalSEO
665 settings={settings}
666 handleSettingChange={handleSettingChange}
667 />
668 );
669
670 default:
671 return null;
672 }
673 })()}
674
675 {/* Validation Results */}
676 <SiteIdentityValidation
677 validationResults={validationResults}
678 activeSubSection={activeSubSection}
679 />
680
681 {/* Optimization Results */}
682 <SiteIdentityOptimization
683 optimizationResults={optimizationResults}
684 activeSubSection={activeSubSection}
685 settings={settings}
686 handleSettingChange={handleSettingChange}
687 setNotice={setNotice}
688 setOptimizationResults={setOptimizationResults}
689 />
690
691
692 {/* AI Optimization Results */}
693 {aiOptimizationResults && (
694 <Card size="small" className="thinkrank-mt-lg">
695 <CardHeader>
696 <Flex justify="space-between" align="center">
697 <FlexItem>
698 <h3>{__('AI Site Information Optimization', 'thinkrank')}</h3>
699 {aiOptimizationResults.ai_model && (
700 <p style={{ fontSize: '12px', color: '#757575', margin: '4px 0 0 0' }}>
701 {__('Generated by:', 'thinkrank')} {aiOptimizationResults.ai_model}
702 </p>
703 )}
704 </FlexItem>
705 {aiOptimizationResults.score !== undefined && (
706 <FlexItem>
707 <div style={{
708 padding: '4px 8px',
709 backgroundColor: aiOptimizationResults.score >= 80 ? '#00a32a' :
710 aiOptimizationResults.score >= 60 ? '#dba617' : '#d63638',
711 color: 'white',
712 borderRadius: '4px',
713 fontSize: '12px',
714 fontWeight: 'bold'
715 }}>
716 {__('Score:', 'thinkrank')} {aiOptimizationResults.score}/100
717 </div>
718 </FlexItem>
719 )}
720 </Flex>
721 </CardHeader>
722 <CardBody>
723 {/* AI Analysis */}
724 {aiOptimizationResults.analysis && (
725 <div style={{ marginBottom: '16px' }}>
726 <h4>{__('AI Analysis:', 'thinkrank')}</h4>
727 <p style={{
728 padding: '12px',
729 backgroundColor: '#f0f6fc',
730 border: '1px solid #d0d7de',
731 borderRadius: '4px',
732 fontStyle: 'italic'
733 }}>
734 {aiOptimizationResults.analysis}
735 </p>
736 </div>
737 )}
738
739 {/* Suggestions List */}
740 {aiOptimizationResults.suggestions && aiOptimizationResults.suggestions.length > 0 && (
741 <div style={{ marginBottom: '16px' }}>
742 <h4>{__('AI Suggestions:', 'thinkrank')}</h4>
743 <ul style={{ paddingLeft: '20px' }}>
744 {aiOptimizationResults.suggestions.map((suggestion, index) => (
745 <li key={index} style={{ marginBottom: '8px' }}>
746 {suggestion}
747 </li>
748 ))}
749 </ul>
750 </div>
751 )}
752
753 {/* Optimized Data Preview */}
754 {aiOptimizationResults.optimized_data && Object.keys(aiOptimizationResults.optimized_data).length > 0 && (
755 <div style={{ marginBottom: '16px' }}>
756 <h4>{__('AI-Generated Content:', 'thinkrank')}</h4>
757 <div style={{
758 padding: '12px',
759 backgroundColor: '#f6f7f7',
760 border: '1px solid #ddd',
761 borderRadius: '4px',
762 fontSize: '13px'
763 }}>
764 {Object.keys(aiOptimizationResults.optimized_data).map(key => (
765 <div key={key} style={{ marginBottom: '8px' }}>
766 <strong>{key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}:</strong>
767 <br />
768 <span style={{ color: '#666' }}>
769 {aiOptimizationResults.optimized_data[key]}
770 </span>
771 </div>
772 ))}
773 </div>
774 </div>
775 )}
776
777 {/* Action Buttons */}
778 {aiOptimizationResults.optimized_data && (
779 <Flex gap={2}>
780 <Button
781 variant="primary"
782 onClick={() => {
783 Object.keys(aiOptimizationResults.optimized_data).forEach(key => {
784 if (settings.hasOwnProperty(key)) {
785 handleSettingChange(key, aiOptimizationResults.optimized_data[key]);
786 }
787 });
788 setNotice({
789 status: 'success',
790 message: __(`${getSubSectionDisplayName()} AI optimization suggestions applied successfully!`, 'thinkrank')
791 });
792 setAiOptimizationResults(null);
793 }}
794 >
795 {__('Apply AI Suggestions', 'thinkrank')}
796 </Button>
797 <Button
798 variant="secondary"
799 onClick={() => setAiOptimizationResults(null)}
800 >
801 {__('Dismiss', 'thinkrank')}
802 </Button>
803 </Flex>
804 )}
805 </CardBody>
806 </Card>
807 )}
808
809 <Flex justify="flex-end" className="thinkrank-mt-lg">
810 <FlexItem>
811 <Button
812 variant="primary"
813 onClick={saveSettings}
814 isBusy={isSaving}
815 disabled={!hasChanges || isSaving}
816 >
817 {isSaving ? __('Saving...', 'thinkrank') : __('Save Settings', 'thinkrank')}
818 </Button>
819 </FlexItem>
820 </Flex>
821 </SettingsCard>
822 </div>
823 );
824 };
825
826 export default SiteIdentityTab;
827