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 / content-brief / ContentStructure.js

ContentStructure.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/content-brief/ContentStructure.js

1,065 lines 61.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Content Structure Component
3 *
4 * Displays the generated content brief structure and recommendations
5 *
6 * @package ThinkRank
7 * @since 1.0.0
8 */
9
10 import React, { useState } from 'react';
11 import { Notice, Button } from '@wordpress/components';
12 import { __ } from '@wordpress/i18n';
13 import {
14 copy,
15 chevronDown,
16 chevronUp,
17 info,
18 heading,
19 formatListBullets,
20 search,
21 chartBar,
22 funnel,
23 megaphone,
24 code
25 } from '@wordpress/icons';
26 import ModelBadge from '../shared/ModelBadge';
27
28 /**
29 * Icon Component for WordPress Icons
30 */
31 const Icon = ({ icon, className = '' }) => {
32 return (
33 <span className={`thinkrank-icon ${className}`} style={{ display: 'inline-flex', alignItems: 'center' }}>
34 {icon}
35 </span>
36 );
37 };
38
39 /**
40 * Utility function to convert markdown formatting to HTML
41 */
42 const formatMarkdownText = (text) => {
43 if (!text) return text;
44
45 // Convert **bold** to <strong>bold</strong>
46 const boldFormatted = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
47
48 // Convert *italic* to <em>italic</em>
49 const italicFormatted = boldFormatted.replace(/\*(.*?)\*/g, '<em>$1</em>');
50
51 return italicFormatted;
52 };
53
54 /**
55 * Component to render formatted text with HTML
56 */
57 const FormattedText = ({ children, className = '' }) => {
58 const formattedText = formatMarkdownText(children);
59
60 // Sanitize HTML to prevent XSS attacks
61 // Only allow safe formatting tags
62 const sanitizedHTML = formattedText
63 .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove script tags
64 .replace(/javascript:/gi, '') // Remove javascript: URLs
65 .replace(/on\w+\s*=/gi, '') // Remove event handlers
66 .replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '') // Remove iframes
67 .replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, '') // Remove objects
68 .replace(/<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi, ''); // Remove embeds
69
70 return (
71 <span
72 className={className}
73 dangerouslySetInnerHTML={{ __html: sanitizedHTML }}
74 />
75 );
76 };
77
78
79
80 /**
81 * Collapsible Section Component
82 */
83 const CollapsibleSection = ({ title, icon, children, defaultOpen = true }) => {
84 const [isOpen, setIsOpen] = useState(defaultOpen);
85
86 return (
87 <div className="thinkrank-card thinkrank-card--elevated thinkrank-mb-6">
88 <div
89 className="thinkrank-card__header thinkrank-cursor-pointer"
90 onClick={() => setIsOpen(!isOpen)}
91 style={{ cursor: 'pointer' }}
92 >
93 <div className="thinkrank-flex thinkrank-items-center thinkrank-justify-between">
94 <h3 className="thinkrank-text-lg thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-0 thinkrank-flex thinkrank-items-center thinkrank-gap-2">
95 {typeof icon === 'string' ? (
96 <span>{icon}</span>
97 ) : (
98 <Icon icon={icon} className="thinkrank-text-blue" />
99 )}
100 {title}
101 </h3>
102 <Button
103 icon={isOpen ? chevronUp : chevronDown}
104 variant="tertiary"
105 size="small"
106 onClick={(e) => {
107 e.stopPropagation();
108 setIsOpen(!isOpen);
109 }}
110 />
111 </div>
112 </div>
113 {isOpen && (
114 <div className="thinkrank-card__body">
115 {children}
116 </div>
117 )}
118 </div>
119 );
120 };
121
122 const ContentStructure = ({ briefData, formData }) => {
123 if (!briefData) {
124 return null;
125 }
126
127 /**
128 * Copy text to clipboard with fallback
129 */
130 const copyToClipboard = async (text) => {
131 try {
132 // Try modern clipboard API first
133 if (navigator.clipboard && navigator.clipboard.writeText) {
134 await navigator.clipboard.writeText(text);
135 console.log('Copied to clipboard');
136 return;
137 }
138
139 // Fallback for older browsers or non-HTTPS contexts
140 const textArea = document.createElement('textarea');
141 textArea.value = text;
142 textArea.style.position = 'fixed';
143 textArea.style.left = '-999999px';
144 textArea.style.top = '-999999px';
145 document.body.appendChild(textArea);
146 textArea.focus();
147 textArea.select();
148
149 try {
150 document.execCommand('copy');
151 console.log('Copied to clipboard (fallback)');
152 } catch (err) {
153 console.error('Failed to copy text: ', err);
154 }
155
156 document.body.removeChild(textArea);
157 } catch (err) {
158 console.error('Copy to clipboard failed: ', err);
159 }
160 };
161
162 /**
163 * Render overview card with form input summary
164 */
165 const renderOverviewCard = () => {
166 // Use generation_params from briefData as primary source, fallback to formData
167 const params = briefData.generation_params || formData;
168 if (!params) return null;
169
170 const getDisplayValue = (key, value) => {
171 const displayMaps = {
172 content_type: {
173 'blog_post': __('Blog Post', 'thinkrank'),
174 'article': __('Article', 'thinkrank'),
175 'landing_page': __('Landing Page', 'thinkrank'),
176 'product_description': __('Product Description', 'thinkrank'),
177 'social_media': __('Social Media', 'thinkrank'),
178 'email': __('Email', 'thinkrank')
179 },
180 target_audience: {
181 'general': __('General Audience', 'thinkrank'),
182 'beginners': __('Beginners', 'thinkrank'),
183 'professionals': __('Professionals', 'thinkrank'),
184 'experts': __('Experts', 'thinkrank'),
185 'students': __('Students', 'thinkrank'),
186 'business_owners': __('Business Owners', 'thinkrank')
187 },
188 content_length: {
189 'short': __('Short (300-600 words)', 'thinkrank'),
190 'medium': __('Medium (600-1200 words)', 'thinkrank'),
191 'long': __('Long (1200+ words)', 'thinkrank')
192 },
193 tone: {
194 'professional': __('Professional', 'thinkrank'),
195 'casual': __('Casual', 'thinkrank'),
196 'friendly': __('Friendly', 'thinkrank'),
197 'authoritative': __('Authoritative', 'thinkrank'),
198 'conversational': __('Conversational', 'thinkrank'),
199 'technical': __('Technical', 'thinkrank')
200 }
201 };
202
203 return displayMaps[key]?.[value] || value;
204 };
205
206 return (
207 <div className="thinkrank-card thinkrank-card--elevated thinkrank-mb-6">
208 <div className="thinkrank-card__body">
209 <div className="thinkrank-flex thinkrank-items-center thinkrank-justify-between thinkrank-mb-4">
210 <h3 className="thinkrank-text-lg thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-0 thinkrank-flex thinkrank-items-center thinkrank-gap-2">
211 <Icon icon={info} className="thinkrank-text-blue" />
212 {__('Brief Overview', 'thinkrank')}
213 </h3>
214 {briefData.generation_meta && (
215 <ModelBadge
216 provider={briefData.generation_meta.provider}
217 model={briefData.generation_meta.model}
218 />
219 )}
220 </div>
221 <div className="thinkrank-grid thinkrank-grid-cols-1-xs thinkrank-grid-cols-2-md thinkrank-gap-4">
222 <div className="thinkrank-space-y-3">
223 <div>
224 <span className="thinkrank-text-sm thinkrank-font-bold thinkrank-text-secondary">{__('Target Keywords:', 'thinkrank')}</span>
225 <div className="thinkrank-mt-1 thinkrank-flex thinkrank-flex-wrap thinkrank-gap-2">
226 {params.target_keywords?.filter(k => k.trim()).map((keyword, index) => (
227 <span key={index} className="thinkrank-px-3 thinkrank-py-1 thinkrank-bg-blue thinkrank-bg-opacity-10 thinkrank-text-blue thinkrank-text-sm thinkrank-rounded-full">
228 {keyword}
229 </span>
230 ))}
231 </div>
232 </div>
233 <div>
234 <span className="thinkrank-text-sm thinkrank-font-bold thinkrank-text-secondary">{__('Content Type:', 'thinkrank')}</span>
235 <p className="thinkrank-text-sm thinkrank-text-primary thinkrank-mt-1 thinkrank-mb-0">{getDisplayValue('content_type', params.content_type)}</p>
236 </div>
237 <div>
238 <span className="thinkrank-text-sm thinkrank-font-bold thinkrank-text-secondary">{__('Target Audience:', 'thinkrank')}</span>
239 <p className="thinkrank-text-sm thinkrank-text-primary thinkrank-mt-1 thinkrank-mb-0">{getDisplayValue('target_audience', params.target_audience)}</p>
240 </div>
241 </div>
242 <div className="thinkrank-space-y-3">
243 <div>
244 <span className="thinkrank-text-sm thinkrank-font-bold thinkrank-text-secondary">{__('Content Length:', 'thinkrank')}</span>
245 <p className="thinkrank-text-sm thinkrank-text-primary thinkrank-mt-1 thinkrank-mb-0">{getDisplayValue('content_length', params.content_length)}</p>
246 </div>
247 <div>
248 <span className="thinkrank-text-sm thinkrank-font-bold thinkrank-text-secondary">{__('Tone:', 'thinkrank')}</span>
249 <p className="thinkrank-text-sm thinkrank-text-primary thinkrank-mt-1 thinkrank-mb-0">{getDisplayValue('tone', params.tone)}</p>
250 </div>
251 {params.competitor_urls?.filter(url => url.trim()).length > 0 && (
252 <div>
253 <span className="thinkrank-text-sm thinkrank-font-bold thinkrank-text-secondary">{__('Competitor URLs:', 'thinkrank')}</span>
254 <div className="thinkrank-mt-1 thinkrank-space-y-1">
255 {params.competitor_urls.filter(url => url.trim()).map((url, index) => (
256 <div key={index} className="thinkrank-text-sm thinkrank-text-primary">
257 <a
258 href={url}
259 target="_blank"
260 rel="noopener noreferrer"
261 className="thinkrank-text-blue hover:thinkrank-underline thinkrank-break-words"
262 title={url}
263 >
264 {url.length > 50 ? url.substring(0, 50) + '...' : url}
265 </a>
266 </div>
267 ))}
268 </div>
269 </div>
270 )}
271 </div>
272 </div>
273 {params.additional_context && (
274 <div className="thinkrank-mt-4 thinkrank-pt-4 thinkrank-border-t thinkrank-border-light">
275 <span className="thinkrank-text-sm thinkrank-font-bold thinkrank-text-secondary">{__('Additional Context:', 'thinkrank')}</span>
276 <p className="thinkrank-text-sm thinkrank-text-primary thinkrank-mt-1 thinkrank-mb-0">{params.additional_context}</p>
277 </div>
278 )}
279
280 {/* Generation Date */}
281 {briefData.created_at && (
282 <div className="thinkrank-flex thinkrank-justify-end thinkrank-mt-4 thinkrank-pt-4 thinkrank-border-t thinkrank-border-light">
283 <span className="thinkrank-text-xs thinkrank-text-secondary">
284 {__('Generated on:', 'thinkrank')} {briefData.created_at}
285 </span>
286 </div>
287 )}
288 </div>
289 </div>
290 );
291 };
292
293 /**
294 * Render title suggestions
295 */
296 const renderTitleSuggestions = () => {
297 if (!briefData.title || !Array.isArray(briefData.title)) {
298 return null;
299 }
300
301 return (
302 <CollapsibleSection
303 title={__('Title Suggestions', 'thinkrank')}
304 icon={heading}
305 defaultOpen={true}
306 >
307 <div className="thinkrank-space-y-3">
308 {briefData.title.map((title, index) => (
309 <div key={index} className="thinkrank-flex thinkrank-items-center thinkrank-justify-between thinkrank-p-3 thinkrank-bg-gray-50 thinkrank-rounded-lg thinkrank-border thinkrank-border-light">
310 <span className="thinkrank-text-sm thinkrank-text-primary thinkrank-flex-1 thinkrank-pr-3">{title}</span>
311 <button
312 className="thinkrank-btn thinkrank-btn--ghost thinkrank-btn--sm thinkrank-w-8 thinkrank-h-8 thinkrank-flex-center"
313 onClick={() => copyToClipboard(title)}
314 title={__('Copy title', 'thinkrank')}
315 >
316 <svg className="thinkrank-w-4 thinkrank-h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
317 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
318 </svg>
319 </button>
320 </div>
321 ))}
322 </div>
323 </CollapsibleSection>
324 );
325 };
326
327 /**
328 * Render content outline
329 */
330 const renderContentOutline = () => {
331 if (!briefData.outline || !Array.isArray(briefData.outline)) {
332 return null;
333 }
334
335 return (
336 <CollapsibleSection
337 title={__('Content Outline', 'thinkrank')}
338 icon={formatListBullets}
339 defaultOpen={true}
340 >
341 <div className="thinkrank-space-y-4">
342 {briefData.outline.map((item, index) => (
343 <div
344 key={index}
345 className="thinkrank-border-l-4 thinkrank-border-blue thinkrank-pl-4 thinkrank-py-3"
346 style={{ marginLeft: `${(item.level - 1) * 20}px` }}
347 >
348 <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-3 thinkrank-mb-2">
349 <span className="thinkrank-text-sm thinkrank-font-semibold thinkrank-text-blue thinkrank-bg-blue thinkrank-bg-opacity-10 thinkrank-px-2 thinkrank-py-1 thinkrank-rounded">
350 H{item.level}
351 </span>
352 <strong className="thinkrank-text-primary thinkrank-font-semibold"><FormattedText>{item.heading}</FormattedText></strong>
353 {item.word_count > 0 && (
354 <span className="thinkrank-text-xs thinkrank-text-secondary thinkrank-bg-gray-100 thinkrank-px-2 thinkrank-py-1 thinkrank-rounded">
355 {item.word_count} words
356 </span>
357 )}
358 </div>
359
360 {item.key_points && item.key_points.length > 0 && (
361 <ul className="thinkrank-list-disc thinkrank-list-inside thinkrank-space-y-1 thinkrank-text-sm thinkrank-text-secondary thinkrank-ml-4">
362 {item.key_points.map((point, pointIndex) => (
363 <li key={pointIndex}><FormattedText>{point}</FormattedText></li>
364 ))}
365 </ul>
366 )}
367
368 {item.keywords && item.keywords.length > 0 && (
369 <div className="thinkrank-mt-2 thinkrank-p-2 thinkrank-bg-gray-50 thinkrank-rounded">
370 <strong className="thinkrank-text-sm thinkrank-text-primary">{__('Keywords:', 'thinkrank')}</strong>
371 <span className="thinkrank-text-sm thinkrank-text-secondary thinkrank-ml-2">{item.keywords.join(', ')}</span>
372 </div>
373 )}
374 </div>
375 ))}
376 </div>
377
378 {briefData.estimated_word_count && (
379 <div className="thinkrank-mt-6 thinkrank-p-4 thinkrank-bg-blue thinkrank-bg-opacity-10 thinkrank-border thinkrank-border-blue thinkrank-border-opacity-20 thinkrank-rounded-lg">
380 <strong className="thinkrank-text-blue thinkrank-font-semibold">
381 {__('Estimated Total Word Count:', 'thinkrank')} {briefData.estimated_word_count}
382 </strong>
383 </div>
384 )}
385 </CollapsibleSection>
386 );
387 };
388
389 /**
390 * Render SEO recommendations
391 */
392 const renderSEORecommendations = () => {
393 if (!briefData.seo_recommendations) {
394 return null;
395 }
396
397 const {
398 title_suggestions,
399 meta_descriptions,
400 meta_description,
401 url_slugs,
402 focus_keyword_analysis,
403 internal_links,
404 related_keywords,
405 long_tail_keywords
406 } = briefData.seo_recommendations;
407
408 return (
409 <CollapsibleSection
410 title={__('SEO Recommendations', 'thinkrank')}
411 icon={search}
412 defaultOpen={false}
413 >
414 <div className="thinkrank-space-y-6">
415 {/* Title Suggestions */}
416 {title_suggestions && title_suggestions.length > 0 && (
417 <div>
418 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Title Suggestions', 'thinkrank')}</h4>
419 <ul className="thinkrank-list-disc thinkrank-list-inside thinkrank-space-y-2 thinkrank-text-sm thinkrank-text-secondary">
420 {title_suggestions.map((suggestion, index) => (
421 <li key={index}>{suggestion}</li>
422 ))}
423 </ul>
424 </div>
425 )}
426
427 {/* Meta Descriptions */}
428 {(meta_descriptions && meta_descriptions.length > 0) || meta_description ? (
429 <div>
430 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Meta Descriptions', 'thinkrank')}</h4>
431
432 {meta_description && (
433 <div className="thinkrank-mb-4 thinkrank-p-4 thinkrank-bg-blue thinkrank-bg-opacity-5 thinkrank-rounded thinkrank-border thinkrank-border-blue thinkrank-border-opacity-20">
434 <div className="thinkrank-flex thinkrank-justify-between thinkrank-items-center thinkrank-mb-2">
435 <span className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-blue">{__('Primary Meta Description', 'thinkrank')}</span>
436 <span className={`thinkrank-text-xs thinkrank-px-2 thinkrank-py-1 thinkrank-rounded-full ${
437 meta_description.length >= 150 && meta_description.length <= 160
438 ? 'thinkrank-bg-green thinkrank-text-white'
439 : 'thinkrank-bg-orange thinkrank-text-white'
440 }`}>
441 {meta_description.length} {__('characters', 'thinkrank')}
442 </span>
443 </div>
444 <p className="thinkrank-text-sm thinkrank-text-secondary thinkrank-m-0"><FormattedText>{meta_description}</FormattedText></p>
445 </div>
446 )}
447
448 {meta_descriptions && meta_descriptions.length > 0 && (
449 <div>
450 <h5 className="thinkrank-text-sm thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-2">{__('Alternative Variations', 'thinkrank')}</h5>
451 {meta_descriptions.map((desc, index) => (
452 <div key={index} className="thinkrank-mb-2 thinkrank-p-3 thinkrank-bg-gray-50 thinkrank-rounded thinkrank-border thinkrank-border-gray-200">
453 <div className="thinkrank-flex thinkrank-justify-between thinkrank-items-center thinkrank-mb-1">
454 <span className="thinkrank-text-xs thinkrank-font-medium thinkrank-text-secondary">{__('Variation', 'thinkrank')} {index + 1}</span>
455 <span className="thinkrank-text-xs thinkrank-px-2 thinkrank-py-0.5 thinkrank-rounded-full thinkrank-bg-gray-200 thinkrank-text-gray-700">
456 {desc.length} {__('chars', 'thinkrank')}
457 </span>
458 </div>
459 <p className="thinkrank-text-sm thinkrank-text-secondary thinkrank-m-0"><FormattedText>{desc}</FormattedText></p>
460 </div>
461 ))}
462 </div>
463 )}
464 </div>
465 ) : null}
466
467 {/* URL Slugs */}
468 {url_slugs && url_slugs.length > 0 && (
469 <div>
470 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('URL Slug Suggestions', 'thinkrank')}</h4>
471 <div className="thinkrank-space-y-2">
472 {url_slugs.map((slug, index) => (
473 <div key={index} className="thinkrank-flex thinkrank-items-center thinkrank-gap-2">
474 <code className="thinkrank-px-3 thinkrank-py-1 thinkrank-bg-gray-100 thinkrank-text-sm thinkrank-rounded">
475 {slug}
476 </code>
477 <button
478 className="thinkrank-text-blue thinkrank-text-sm thinkrank-flex thinkrank-items-center thinkrank-gap-1"
479 onClick={() => copyToClipboard(slug)}
480 >
481 <Icon icon={copy} />
482 {__('Copy', 'thinkrank')}
483 </button>
484 </div>
485 ))}
486 </div>
487 </div>
488 )}
489
490 {/* Focus Keyword Analysis */}
491 {focus_keyword_analysis && (focus_keyword_analysis.primary_placement?.length > 0 ||
492 focus_keyword_analysis.secondary_integration?.length > 0 ||
493 focus_keyword_analysis.density_guidelines?.length > 0) && (
494 <div>
495 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Focus Keyword Analysis', 'thinkrank')}</h4>
496
497 {focus_keyword_analysis.primary_placement?.length > 0 && (
498 <div className="thinkrank-mb-3">
499 <h5 className="thinkrank-text-sm thinkrank-font-semibold thinkrank-text-secondary thinkrank-mb-2">{__('Primary Keyword Placement', 'thinkrank')}</h5>
500 <ul className="thinkrank-list-disc thinkrank-list-inside thinkrank-space-y-1 thinkrank-text-sm thinkrank-text-secondary">
501 {focus_keyword_analysis.primary_placement.map((item, index) => (
502 <li key={index}><FormattedText>{item}</FormattedText></li>
503 ))}
504 </ul>
505 </div>
506 )}
507
508 {focus_keyword_analysis.secondary_integration?.length > 0 && (
509 <div className="thinkrank-mb-3">
510 <h5 className="thinkrank-text-sm thinkrank-font-semibold thinkrank-text-secondary thinkrank-mb-2">{__('Secondary Keyword Integration', 'thinkrank')}</h5>
511 <ul className="thinkrank-list-disc thinkrank-list-inside thinkrank-space-y-1 thinkrank-text-sm thinkrank-text-secondary">
512 {focus_keyword_analysis.secondary_integration.map((item, index) => (
513 <li key={index}><FormattedText>{item}</FormattedText></li>
514 ))}
515 </ul>
516 </div>
517 )}
518
519 {focus_keyword_analysis.density_guidelines?.length > 0 && (
520 <div>
521 <h5 className="thinkrank-text-sm thinkrank-font-semibold thinkrank-text-secondary thinkrank-mb-2">{__('Keyword Density Guidelines', 'thinkrank')}</h5>
522 <ul className="thinkrank-list-disc thinkrank-list-inside thinkrank-space-y-1 thinkrank-text-sm thinkrank-text-secondary">
523 {focus_keyword_analysis.density_guidelines.map((item, index) => (
524 <li key={index}><FormattedText>{item}</FormattedText></li>
525 ))}
526 </ul>
527 </div>
528 )}
529 </div>
530 )}
531
532 {/* Internal Linking */}
533 {internal_links && internal_links.length > 0 && (
534 <div>
535 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Internal Linking Opportunities', 'thinkrank')}</h4>
536 <ul className="thinkrank-list-disc thinkrank-list-inside thinkrank-space-y-2 thinkrank-text-sm thinkrank-text-secondary">
537 {internal_links.map((link, index) => (
538 <li key={index}><FormattedText>{link}</FormattedText></li>
539 ))}
540 </ul>
541 </div>
542 )}
543
544 {/* Related Keywords */}
545 {related_keywords && related_keywords.length > 0 && (
546 <div>
547 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Related Keywords', 'thinkrank')}</h4>
548 <div className="thinkrank-flex thinkrank-flex-wrap thinkrank-gap-2">
549 {related_keywords.map((keyword, index) => (
550 <span key={index} className="thinkrank-px-3 thinkrank-py-1 thinkrank-bg-blue thinkrank-bg-opacity-10 thinkrank-text-blue thinkrank-text-sm thinkrank-rounded-full">
551 <FormattedText>{keyword}</FormattedText>
552 </span>
553 ))}
554 </div>
555 </div>
556 )}
557
558 {/* Long-tail Keywords */}
559 {long_tail_keywords && long_tail_keywords.length > 0 && (
560 <div>
561 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Long-tail Keyword Variations', 'thinkrank')}</h4>
562 <div className="thinkrank-flex thinkrank-flex-wrap thinkrank-gap-2">
563 {long_tail_keywords.map((keyword, index) => (
564 <span key={index} className="thinkrank-px-3 thinkrank-py-1 thinkrank-bg-green thinkrank-bg-opacity-10 thinkrank-text-green thinkrank-text-sm thinkrank-rounded-full">
565 {keyword}
566 </span>
567 ))}
568 </div>
569 </div>
570 )}
571 </div>
572 </CollapsibleSection>
573 );
574 };
575
576 /**
577 * Render social media meta tags
578 */
579 const renderSocialMediaTags = () => {
580 if (!briefData.social_media) {
581 return null;
582 }
583
584 const { open_graph, twitter_card } = briefData.social_media;
585
586 // Check if we have any social media data
587 if ((!open_graph?.title && !open_graph?.description) &&
588 (!twitter_card?.title && !twitter_card?.description)) {
589 return null;
590 }
591
592 return (
593 <CollapsibleSection
594 title={__('Social Media Meta Tags', 'thinkrank')}
595 icon={megaphone}
596 defaultOpen={false}
597 >
598 <div className="thinkrank-space-y-6">
599 {/* Open Graph Tags */}
600 {(open_graph?.title || open_graph?.description) && (
601 <div>
602 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Open Graph (Facebook, LinkedIn)', 'thinkrank')}</h4>
603
604 {open_graph.title && (
605 <div className="thinkrank-mb-3 thinkrank-p-3 thinkrank-bg-blue thinkrank-bg-opacity-5 thinkrank-rounded thinkrank-border thinkrank-border-blue thinkrank-border-opacity-20">
606 <div className="thinkrank-flex thinkrank-justify-between thinkrank-items-center thinkrank-mb-2">
607 <span className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-blue">{__('OG Title', 'thinkrank')}</span>
608 <span className={`thinkrank-text-xs thinkrank-px-2 thinkrank-py-1 thinkrank-rounded-full ${
609 open_graph.title.length <= 55
610 ? 'thinkrank-bg-green thinkrank-text-white'
611 : 'thinkrank-bg-orange thinkrank-text-white'
612 }`}>
613 {open_graph.title.length}/55 {__('chars', 'thinkrank')}
614 </span>
615 </div>
616 <p className="thinkrank-text-sm thinkrank-text-secondary thinkrank-m-0"><FormattedText>{open_graph.title}</FormattedText></p>
617 </div>
618 )}
619
620 {open_graph.description && (
621 <div className="thinkrank-p-3 thinkrank-bg-blue thinkrank-bg-opacity-5 thinkrank-rounded thinkrank-border thinkrank-border-blue thinkrank-border-opacity-20">
622 <div className="thinkrank-flex thinkrank-justify-between thinkrank-items-center thinkrank-mb-2">
623 <span className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-blue">{__('OG Description', 'thinkrank')}</span>
624 <span className={`thinkrank-text-xs thinkrank-px-2 thinkrank-py-1 thinkrank-rounded-full ${
625 open_graph.description.length <= 125
626 ? 'thinkrank-bg-green thinkrank-text-white'
627 : 'thinkrank-bg-orange thinkrank-text-white'
628 }`}>
629 {open_graph.description.length}/125 {__('chars', 'thinkrank')}
630 </span>
631 </div>
632 <p className="thinkrank-text-sm thinkrank-text-secondary thinkrank-m-0"><FormattedText>{open_graph.description}</FormattedText></p>
633 </div>
634 )}
635 </div>
636 )}
637
638 {/* Twitter Card Tags */}
639 {(twitter_card?.title || twitter_card?.description) && (
640 <div>
641 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Twitter Cards', 'thinkrank')}</h4>
642
643 {twitter_card.title && (
644 <div className="thinkrank-mb-3 thinkrank-p-3 thinkrank-bg-gray-50 thinkrank-rounded thinkrank-border thinkrank-border-gray-200">
645 <div className="thinkrank-flex thinkrank-justify-between thinkrank-items-center thinkrank-mb-2">
646 <span className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-secondary">{__('Twitter Title', 'thinkrank')}</span>
647 <span className={`thinkrank-text-xs thinkrank-px-2 thinkrank-py-1 thinkrank-rounded-full ${
648 twitter_card.title.length <= 70
649 ? 'thinkrank-bg-green thinkrank-text-white'
650 : 'thinkrank-bg-orange thinkrank-text-white'
651 }`}>
652 {twitter_card.title.length}/70 {__('chars', 'thinkrank')}
653 </span>
654 </div>
655 <p className="thinkrank-text-sm thinkrank-text-secondary thinkrank-m-0"><FormattedText>{twitter_card.title}</FormattedText></p>
656 </div>
657 )}
658
659 {twitter_card.description && (
660 <div className="thinkrank-p-3 thinkrank-bg-gray-50 thinkrank-rounded thinkrank-border thinkrank-border-gray-200">
661 <div className="thinkrank-flex thinkrank-justify-between thinkrank-items-center thinkrank-mb-2">
662 <span className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-secondary">{__('Twitter Description', 'thinkrank')}</span>
663 <span className={`thinkrank-text-xs thinkrank-px-2 thinkrank-py-1 thinkrank-rounded-full ${
664 twitter_card.description.length <= 125
665 ? 'thinkrank-bg-green thinkrank-text-white'
666 : 'thinkrank-bg-orange thinkrank-text-white'
667 }`}>
668 {twitter_card.description.length}/125 {__('chars', 'thinkrank')}
669 </span>
670 </div>
671 <p className="thinkrank-text-sm thinkrank-text-secondary thinkrank-m-0"><FormattedText>{twitter_card.description}</FormattedText></p>
672 </div>
673 )}
674 </div>
675 )}
676 </div>
677 </CollapsibleSection>
678 );
679 };
680
681 /**
682 * Render schema markup suggestions
683 */
684 const renderSchemaMarkup = () => {
685 if (!briefData.schema_markup) {
686 return null;
687 }
688
689 const { recommended_types, key_properties, faq_questions } = briefData.schema_markup;
690
691 // Check if we have any schema data
692 if ((!recommended_types || recommended_types.length === 0) &&
693 (!key_properties || key_properties.length === 0) &&
694 (!faq_questions || faq_questions.length === 0)) {
695 return null;
696 }
697
698 return (
699 <CollapsibleSection
700 title={__('Schema Markup Suggestions', 'thinkrank')}
701 icon={code}
702 defaultOpen={false}
703 >
704 <div className="thinkrank-space-y-6">
705 {/* Recommended Schema Types */}
706 {recommended_types && recommended_types.length > 0 && (
707 <div>
708 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Recommended Schema Types', 'thinkrank')}</h4>
709 <div className="thinkrank-flex thinkrank-flex-wrap thinkrank-gap-2">
710 {recommended_types.map((type, index) => (
711 <span key={index} className="thinkrank-px-3 thinkrank-py-1 thinkrank-bg-purple thinkrank-bg-opacity-10 thinkrank-text-purple thinkrank-text-sm thinkrank-rounded-full thinkrank-font-medium">
712 <FormattedText>{type}</FormattedText>
713 </span>
714 ))}
715 </div>
716 </div>
717 )}
718
719 {/* Key Properties */}
720 {key_properties && key_properties.length > 0 && (
721 <div>
722 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Key Schema Properties', 'thinkrank')}</h4>
723 <ul className="thinkrank-list-disc thinkrank-list-inside thinkrank-space-y-2 thinkrank-text-sm thinkrank-text-secondary">
724 {key_properties.map((property, index) => (
725 <li key={index}><FormattedText>{property}</FormattedText></li>
726 ))}
727 </ul>
728 </div>
729 )}
730
731 {/* FAQ Schema Questions */}
732 {faq_questions && faq_questions.length > 0 && (
733 <div>
734 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('FAQ Schema Questions', 'thinkrank')}</h4>
735 <div className="thinkrank-space-y-3">
736 {faq_questions.map((question, index) => (
737 <div key={index} className="thinkrank-p-3 thinkrank-bg-yellow thinkrank-bg-opacity-5 thinkrank-rounded thinkrank-border thinkrank-border-yellow thinkrank-border-opacity-20">
738 <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-2 thinkrank-mb-1">
739 <span className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-yellow">{__('Q', 'thinkrank')}{index + 1}</span>
740 </div>
741 <p className="thinkrank-text-sm thinkrank-text-secondary thinkrank-m-0"><FormattedText>{question}</FormattedText></p>
742 </div>
743 ))}
744 </div>
745 </div>
746 )}
747 </div>
748 </CollapsibleSection>
749 );
750 };
751
752 /**
753 * Render visual content strategy
754 */
755 const renderVisualContent = () => {
756 if (!briefData.visual_content) {
757 return null;
758 }
759
760 const { image_recommendations, alt_text_suggestions, infographic_opportunities } = briefData.visual_content;
761
762 // Check if we have any visual content data
763 if ((!image_recommendations || image_recommendations.length === 0) &&
764 (!alt_text_suggestions || alt_text_suggestions.length === 0) &&
765 (!infographic_opportunities || infographic_opportunities.length === 0)) {
766 return null;
767 }
768
769 return (
770 <CollapsibleSection
771 title={__('Visual Content Strategy', 'thinkrank')}
772 icon={chartBar}
773 defaultOpen={false}
774 >
775 <div className="thinkrank-space-y-6">
776 {/* Image Recommendations */}
777 {image_recommendations && image_recommendations.length > 0 && (
778 <div>
779 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Image Recommendations', 'thinkrank')}</h4>
780 <ul className="thinkrank-list-disc thinkrank-list-inside thinkrank-space-y-2 thinkrank-text-sm thinkrank-text-secondary">
781 {image_recommendations.map((recommendation, index) => (
782 <li key={index}><FormattedText>{recommendation}</FormattedText></li>
783 ))}
784 </ul>
785 </div>
786 )}
787
788 {/* Alt Text Suggestions */}
789 {alt_text_suggestions && alt_text_suggestions.length > 0 && (
790 <div>
791 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Alt Text Suggestions', 'thinkrank')}</h4>
792 <div className="thinkrank-space-y-2">
793 {alt_text_suggestions.map((suggestion, index) => (
794 <div key={index} className="thinkrank-p-3 thinkrank-bg-green thinkrank-bg-opacity-5 thinkrank-rounded thinkrank-border thinkrank-border-green thinkrank-border-opacity-20">
795 <p className="thinkrank-text-sm thinkrank-text-secondary thinkrank-m-0"><FormattedText>{suggestion}</FormattedText></p>
796 </div>
797 ))}
798 </div>
799 </div>
800 )}
801
802 {/* Infographic Opportunities */}
803 {infographic_opportunities && infographic_opportunities.length > 0 && (
804 <div>
805 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-3">{__('Infographic Opportunities', 'thinkrank')}</h4>
806 <ul className="thinkrank-list-disc thinkrank-list-inside thinkrank-space-y-2 thinkrank-text-sm thinkrank-text-secondary">
807 {infographic_opportunities.map((opportunity, index) => (
808 <li key={index}><FormattedText>{opportunity}</FormattedText></li>
809 ))}
810 </ul>
811 </div>
812 )}
813 </div>
814 </CollapsibleSection>
815 );
816 };
817
818 /**
819 * Render comprehensive competitor analysis
820 */
821 const renderCompetitorAnalysis = () => {
822 const hasCompetitorUrls = briefData.generation_params?.competitor_urls?.length > 0;
823
824 if (!hasCompetitorUrls) {
825 return null;
826 }
827
828 return (
829 <CollapsibleSection
830 title={__('Comprehensive Competitor Analysis', 'thinkrank')}
831 icon={chartBar}
832 defaultOpen={false}
833 >
834 <div className="thinkrank-competitor-analysis">
835 <p>{__('Detailed analysis of competitor content and SEO performance:', 'thinkrank')}</p>
836
837 {/* Competitor URLs Overview */}
838 <div className="thinkrank-mb-6">
839 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-4">{__('Analyzed Competitors', 'thinkrank')}</h4>
840 <div className="thinkrank-flex thinkrank-flex-col thinkrank-gap-3">
841 {briefData.generation_params.competitor_urls.map((url, index) => (
842 <div key={index} className="thinkrank-card thinkrank-card--flat thinkrank-flex thinkrank-items-center thinkrank-justify-between thinkrank-p-4 thinkrank-bg-gray-50">
843 <div className="thinkrank-flex-1 thinkrank-min-w-0">
844 <div className="thinkrank-text-sm thinkrank-font-medium thinkrank-text-secondary thinkrank-mb-1">
845 {__('Competitor', 'thinkrank')} {index + 1}:
846 </div>
847 <a href={url} target="_blank" rel="noopener noreferrer" className="thinkrank-text-sm thinkrank-text-blue hover:thinkrank-underline thinkrank-truncate thinkrank-block">
848 {url.length > 60 ? url.substring(0, 60) + '...' : url}
849 </a>
850 </div>
851 <div className="thinkrank-flex-shrink-0 thinkrank-ml-4">
852 <span className="thinkrank-inline-flex thinkrank-items-center thinkrank-gap-1 thinkrank-px-3 thinkrank-py-1 thinkrank-bg-green thinkrank-bg-opacity-10 thinkrank-text-green thinkrank-text-xs thinkrank-font-medium thinkrank-rounded-full">
853 {__('Analyzed', 'thinkrank')}
854 </span>
855 </div>
856 </div>
857 ))}
858 </div>
859 </div>
860
861 {/* Analysis Summary */}
862 <div className="thinkrank-mb-6">
863 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-4">{__('Key Insights', 'thinkrank')}</h4>
864 <div className="thinkrank-grid thinkrank-grid-cols-1 thinkrank-grid-cols-2-md thinkrank-gap-4">
865 <div className="thinkrank-card thinkrank-card--flat thinkrank-flex thinkrank-items-start thinkrank-gap-3 thinkrank-p-4">
866 <div className="thinkrank-flex-shrink-0 thinkrank-text-lg">📊</div>
867 <div className="thinkrank-flex-1 thinkrank-min-w-0">
868 <div className="thinkrank-text-sm thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-1">{__('Content Analysis', 'thinkrank')}</div>
869 <p className="thinkrank-text-xs thinkrank-text-secondary thinkrank-m-0 thinkrank-leading-relaxed">{__('Word count, readability, and structure analysis completed', 'thinkrank')}</p>
870 </div>
871 </div>
872 <div className="thinkrank-card thinkrank-card--flat thinkrank-flex thinkrank-items-start thinkrank-gap-3 thinkrank-p-4">
873 <div className="thinkrank-flex-shrink-0 thinkrank-text-lg">🎯</div>
874 <div className="thinkrank-flex-1 thinkrank-min-w-0">
875 <div className="thinkrank-text-sm thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-1">{__('SEO Evaluation', 'thinkrank')}</div>
876 <p className="thinkrank-text-xs thinkrank-text-secondary thinkrank-m-0 thinkrank-leading-relaxed">{__('Title tags, meta descriptions, and schema markup assessed', 'thinkrank')}</p>
877 </div>
878 </div>
879 <div className="thinkrank-card thinkrank-card--flat thinkrank-flex thinkrank-items-start thinkrank-gap-3 thinkrank-p-4">
880 <div className="thinkrank-flex-shrink-0 thinkrank-text-lg">🔗</div>
881 <div className="thinkrank-flex-1 thinkrank-min-w-0">
882 <div className="thinkrank-text-sm thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-1">{__('Link Analysis', 'thinkrank')}</div>
883 <p className="thinkrank-text-xs thinkrank-text-secondary thinkrank-m-0 thinkrank-leading-relaxed">{__('Internal and external linking strategies evaluated', 'thinkrank')}</p>
884 </div>
885 </div>
886 <div className="thinkrank-card thinkrank-card--flat thinkrank-flex thinkrank-items-start thinkrank-gap-3 thinkrank-p-4">
887 <div className="thinkrank-flex-shrink-0 thinkrank-text-lg">🖼️</div>
888 <div className="thinkrank-flex-1 thinkrank-min-w-0">
889 <div className="thinkrank-text-sm thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-1">{__('Media Usage', 'thinkrank')}</div>
890 <p className="thinkrank-text-xs thinkrank-text-secondary thinkrank-m-0 thinkrank-leading-relaxed">{__('Image optimization and alt text usage analyzed', 'thinkrank')}</p>
891 </div>
892 </div>
893 </div>
894 </div>
895
896 {/* Detailed Analysis Note */}
897 <div className="thinkrank-p-4 thinkrank-bg-blue thinkrank-bg-opacity-5 thinkrank-border thinkrank-border-blue thinkrank-border-opacity-10 thinkrank-rounded-md">
898 <p className="thinkrank-text-sm thinkrank-text-secondary thinkrank-leading-relaxed thinkrank-m-0">
899 <strong className="thinkrank-text-primary">{__('Note:', 'thinkrank')}</strong> {__('This analysis was used to generate the content gaps and recommendations below. The AI has identified specific opportunities to outperform these competitors.', 'thinkrank')}
900 </p>
901 </div>
902 </div>
903 </CollapsibleSection>
904 );
905 };
906
907 /**
908 * Render enhanced competitor gaps and opportunities
909 */
910 const renderCompetitorGaps = () => {
911 if (!briefData.competitor_gaps || briefData.competitor_gaps.length === 0) {
912 return null;
913 }
914
915 return (
916 <CollapsibleSection
917 title={__('Content Gaps & Competitive Opportunities', 'thinkrank')}
918 icon={funnel}
919 defaultOpen={false}
920 >
921 <div>
922 <div className="thinkrank-mb-6">
923 <p className="thinkrank-text-sm thinkrank-text-secondary thinkrank-leading-relaxed thinkrank-m-0">{__('Based on comprehensive competitor analysis, here are specific opportunities to create superior content that outranks the competition:', 'thinkrank')}</p>
924 </div>
925
926 <div className="thinkrank-flex thinkrank-flex-col thinkrank-gap-4 thinkrank-mb-6">
927 {briefData.competitor_gaps.map((gap, index) => (
928 <div key={index} className="thinkrank-card thinkrank-card--flat thinkrank-flex thinkrank-items-start thinkrank-gap-4 thinkrank-p-4">
929 <div className="thinkrank-flex-shrink-0">
930 <span className="thinkrank-inline-flex thinkrank-items-center thinkrank-px-3 thinkrank-py-1 thinkrank-bg-orange thinkrank-text-white thinkrank-text-xs thinkrank-font-semibold thinkrank-rounded-full thinkrank-shadow-sm">
931 {__('HIGH IMPACT', 'thinkrank')}
932 </span>
933 </div>
934 <div className="thinkrank-flex-1 thinkrank-min-w-0">
935 <div className="thinkrank-text-sm thinkrank-font-medium thinkrank-text-primary thinkrank-leading-relaxed thinkrank-mb-3"><FormattedText>{gap}</FormattedText></div>
936 <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-2">
937 <span className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-secondary">{__('ACTION:', 'thinkrank')}</span>
938 <span className="thinkrank-text-xs thinkrank-text-blue thinkrank-font-medium">{__('Include this in your content strategy', 'thinkrank')}</span>
939 </div>
940 </div>
941 <div className="thinkrank-flex-shrink-0 thinkrank-flex thinkrank-items-center thinkrank-justify-center thinkrank-w-8 thinkrank-h-8 thinkrank-bg-orange thinkrank-bg-opacity-10 thinkrank-rounded-full">
942 <span className="thinkrank-text-base">💡</span>
943 </div>
944 </div>
945 ))}
946 </div>
947
948 <div className="thinkrank-p-4 thinkrank-bg-green thinkrank-bg-opacity-5 thinkrank-border thinkrank-border-green thinkrank-border-opacity-20 thinkrank-rounded-md">
949 <div className="thinkrank-mb-3">
950 <h4 className="thinkrank-text-base thinkrank-font-semibold thinkrank-text-green thinkrank-flex thinkrank-items-center thinkrank-gap-2 thinkrank-m-0">
951 🚀 {__('Your Competitive Advantage', 'thinkrank')}
952 </h4>
953 </div>
954 <div className="thinkrank-flex thinkrank-flex-col thinkrank-gap-2">
955 <div className="thinkrank-flex thinkrank-items-start thinkrank-gap-2">
956 <span className="thinkrank-text-green thinkrank-font-bold thinkrank-flex-shrink-0"></span>
957 <span className="thinkrank-text-sm thinkrank-text-secondary thinkrank-leading-relaxed">{__('Address gaps competitors miss', 'thinkrank')}</span>
958 </div>
959 <div className="thinkrank-flex thinkrank-items-start thinkrank-gap-2">
960 <span className="thinkrank-text-green thinkrank-font-bold thinkrank-flex-shrink-0"></span>
961 <span className="thinkrank-text-sm thinkrank-text-secondary thinkrank-leading-relaxed">{__('Provide more comprehensive coverage', 'thinkrank')}</span>
962 </div>
963 <div className="thinkrank-flex thinkrank-items-start thinkrank-gap-2">
964 <span className="thinkrank-text-green thinkrank-font-bold thinkrank-flex-shrink-0"></span>
965 <span className="thinkrank-text-sm thinkrank-text-secondary thinkrank-leading-relaxed">{__('Better user experience and engagement', 'thinkrank')}</span>
966 </div>
967 <div className="thinkrank-flex thinkrank-items-start thinkrank-gap-2">
968 <span className="thinkrank-text-green thinkrank-font-bold thinkrank-flex-shrink-0"></span>
969 <span className="thinkrank-text-sm thinkrank-text-secondary thinkrank-leading-relaxed">{__('Higher search engine rankings potential', 'thinkrank')}</span>
970 </div>
971 </div>
972 </div>
973 </div>
974 </CollapsibleSection>
975 );
976 };
977
978 /**
979 * Render call-to-action suggestions
980 */
981 const renderCallToActions = () => {
982 if (!briefData.call_to_actions || briefData.call_to_actions.length === 0) {
983 return null;
984 }
985
986 return (
987 <CollapsibleSection
988 title={__('Call-to-Action Suggestions', 'thinkrank')}
989 icon={megaphone}
990 defaultOpen={false}
991 >
992 <div className="thinkrank-space-y-3">
993 {briefData.call_to_actions.map((cta, index) => (
994 <div key={index} className="thinkrank-flex thinkrank-items-center thinkrank-justify-between thinkrank-p-3 thinkrank-bg-gray-50 thinkrank-rounded-lg thinkrank-border thinkrank-border-light">
995 <span className="thinkrank-text-sm thinkrank-text-primary thinkrank-flex-1 thinkrank-pr-3"><FormattedText>{cta}</FormattedText></span>
996 <button
997 className="thinkrank-btn thinkrank-btn--ghost thinkrank-btn--sm thinkrank-w-8 thinkrank-h-8 thinkrank-flex-center"
998 onClick={() => copyToClipboard(cta)}
999 title={__('Copy CTA', 'thinkrank')}
1000 >
1001 <svg className="thinkrank-w-4 thinkrank-h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1002 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
1003 </svg>
1004 </button>
1005 </div>
1006 ))}
1007 </div>
1008 </CollapsibleSection>
1009 );
1010 };
1011
1012 /**
1013 * Render raw AI response (for debugging/reference)
1014 */
1015 const renderRawResponse = () => {
1016 if (!briefData.raw_response) {
1017 return null;
1018 }
1019
1020 return (
1021 <CollapsibleSection
1022 title={__('AI Response (Raw)', 'thinkrank')}
1023 icon={code}
1024 defaultOpen={false}
1025 >
1026 <div className="thinkrank-space-y-4">
1027 <pre className="thinkrank-bg-gray-50 thinkrank-p-4 thinkrank-rounded-lg thinkrank-text-sm thinkrank-overflow-x-auto thinkrank-whitespace-pre-wrap">{briefData.raw_response}</pre>
1028 <button
1029 className="thinkrank-btn thinkrank-btn--secondary thinkrank-btn--sm thinkrank-flex thinkrank-items-center thinkrank-gap-2"
1030 onClick={() => copyToClipboard(briefData.raw_response)}
1031 >
1032 <svg className="thinkrank-w-4 thinkrank-h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1033 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
1034 </svg>
1035 {__('Copy Raw Response', 'thinkrank')}
1036 </button>
1037 </div>
1038 </CollapsibleSection>
1039 );
1040 };
1041
1042 return (
1043 <div className="thinkrank-content-structure">
1044 {/* Overview Card with Model Badge and Date */}
1045 {renderOverviewCard()}
1046
1047 {/* Collapsible Sections */}
1048 <div className="thinkrank-brief-sections">
1049 {renderTitleSuggestions()}
1050 {renderContentOutline()}
1051 {renderSEORecommendations()}
1052 {renderSocialMediaTags()}
1053 {renderSchemaMarkup()}
1054 {renderVisualContent()}
1055 {renderCompetitorAnalysis()}
1056 {renderCompetitorGaps()}
1057 {renderCallToActions()}
1058 {renderRawResponse()}
1059 </div>
1060 </div>
1061 );
1062 };
1063
1064 export default ContentStructure;
1065