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 / ai / SerpPreview.js

SerpPreview.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/ai/SerpPreview.js

223 lines 10.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * SERP Preview Component
3 *
4 * Shows how metadata will appear in search engine results
5 *
6 * @package ThinkRank
7 * @since 1.0.0
8 */
9
10 import { useState } from '@wordpress/element';
11 import { __ } from '@wordpress/i18n';
12 // WordPress components no longer needed - using utility-based styling
13
14 /**
15 * SERP Preview Component
16 */
17 const SerpPreview = ({
18 title = '',
19 description = '',
20 url = '',
21 siteName = '',
22 favicon = null
23 }) => {
24 // Get current site info if not provided
25 const currentUrl = url || window.location.origin;
26 const currentSiteName = siteName || (window.thinkrankAdmin?.siteName || 'Your Website');
27
28 // Format URL for display
29 const displayUrl = currentUrl.replace(/^https?:\/\//, '').replace(/\/$/, '');
30
31 // Truncate title and description to Google's limits
32 const truncatedTitle = title.length > 60 ? title.substring(0, 57) + '...' : title;
33 const truncatedDescription = description.length > 160 ? description.substring(0, 157) + '...' : description;
34
35 return (
36 <div className="thinkrank-ui">
37 <div className="thinkrank-card thinkrank-card--elevated">
38 <div className="thinkrank-card__header">
39 <h4 className="thinkrank-text-lg thinkrank-font-semibold thinkrank-text-primary thinkrank-mb-2">
40 {__('Search Engine Preview', 'thinkrank')}
41 </h4>
42 <p className="thinkrank-text-sm thinkrank-text-secondary">
43 {__('This is how your content will appear in Google search results', 'thinkrank')}
44 </p>
45 </div>
46 <div className="thinkrank-card__body">
47 <div className="thinkrank-bg-white thinkrank-border thinkrank-rounded-lg thinkrank-p-4 thinkrank-shadow-sm">
48 <div className="thinkrank-max-w-lg">
49 {/* Favicon and URL */}
50 <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-2 thinkrank-mb-2">
51 {favicon && (
52 <img
53 src={favicon}
54 alt=""
55 className="thinkrank-w-4 thinkrank-h-4 thinkrank-rounded-sm"
56 width="16"
57 height="16"
58 />
59 )}
60 <span className="thinkrank-text-sm thinkrank-text-gray-600 thinkrank-font-medium">
61 {currentSiteName}
62 </span>
63 <span className="thinkrank-text-sm thinkrank-text-gray-400"></span>
64 <span className="thinkrank-text-sm thinkrank-text-gray-600">
65 {displayUrl}
66 </span>
67 </div>
68
69 {/* Title */}
70 <div className="thinkrank-mb-2">
71 <a
72 href="#"
73 onClick={(e) => e.preventDefault()}
74 className="thinkrank-text-xl thinkrank-text-blue thinkrank-font-normal hover:thinkrank-underline thinkrank-leading-tight"
75 >
76 {truncatedTitle || __('Your SEO Title Will Appear Here', 'thinkrank')}
77 </a>
78 </div>
79
80 {/* Description */}
81 <div className="thinkrank-text-sm thinkrank-text-gray-600 thinkrank-leading-relaxed thinkrank-mb-2">
82 {truncatedDescription || __('Your meta description will appear here. Make it compelling to encourage clicks from search results.', 'thinkrank')}
83 </div>
84
85 {/* Additional elements that might appear */}
86 <div className="thinkrank-text-xs thinkrank-text-gray-500">
87 {new Date().toLocaleDateString('en-US', {
88 month: 'short',
89 day: 'numeric',
90 year: 'numeric'
91 })}
92 </div>
93 </div>
94
95 {/* Character count indicators */}
96 <div className="thinkrank-mt-4 thinkrank-pt-4 thinkrank-border-t thinkrank-border-gray-200">
97 <div className="thinkrank-grid thinkrank-grid-cols-2 thinkrank-gap-4">
98 <div className="thinkrank-flex thinkrank-justify-between thinkrank-items-center">
99 <span className="thinkrank-text-xs thinkrank-text-secondary">
100 {__('Title Length:', 'thinkrank')}
101 </span>
102 <span className={`thinkrank-text-xs thinkrank-font-medium ${title.length > 60 ? 'thinkrank-text-red' : 'thinkrank-text-green'}`}>
103 {title.length}/60
104 </span>
105 </div>
106 <div className="thinkrank-flex thinkrank-justify-between thinkrank-items-center">
107 <span className="thinkrank-text-xs thinkrank-text-secondary">
108 {__('Description Length:', 'thinkrank')}
109 </span>
110 <span className={`thinkrank-text-xs thinkrank-font-medium ${description.length > 160 ? 'thinkrank-text-red' : 'thinkrank-text-green'}`}>
111 {description.length}/160
112 </span>
113 </div>
114 </div>
115 </div>
116 </div>
117 </div>
118 </div>
119 </div>
120 );
121 };
122
123 /**
124 * Mobile SERP Preview Component
125 */
126 const MobileSerpPreview = ({
127 title = '',
128 description = '',
129 url = '',
130 siteName = ''
131 }) => {
132 const currentUrl = url || window.location.origin;
133 const currentSiteName = siteName || (window.thinkrankAdmin?.siteName || 'Your Website');
134 const displayUrl = currentUrl.replace(/^https?:\/\//, '').replace(/\/$/, '');
135
136 // Mobile has slightly different limits
137 const truncatedTitle = title.length > 78 ? title.substring(0, 75) + '...' : title;
138 const truncatedDescription = description.length > 120 ? description.substring(0, 117) + '...' : description;
139
140 return (
141 <div className="thinkrank-ui">
142 <div className="thinkrank-card thinkrank-card--elevated">
143 <div className="thinkrank-card__header">
144 <h4 className="thinkrank-text-lg thinkrank-font-semibold thinkrank-text-primary">
145 {__('Mobile Search Preview', 'thinkrank')}
146 </h4>
147 </div>
148 <div className="thinkrank-card__body">
149 <div className="thinkrank-bg-white thinkrank-border thinkrank-rounded-lg thinkrank-p-3 thinkrank-shadow-sm thinkrank-max-w-sm">
150 <div className="thinkrank-mb-2">
151 <div className="thinkrank-text-sm thinkrank-text-gray-600 thinkrank-font-medium">
152 {currentSiteName}
153 </div>
154 <div className="thinkrank-text-xs thinkrank-text-gray-500">
155 {displayUrl}
156 </div>
157 </div>
158
159 <div className="thinkrank-mb-2">
160 <a
161 href="#"
162 onClick={(e) => e.preventDefault()}
163 className="thinkrank-text-lg thinkrank-text-blue thinkrank-font-normal hover:thinkrank-underline thinkrank-leading-tight"
164 >
165 {truncatedTitle || __('Your SEO Title', 'thinkrank')}
166 </a>
167 </div>
168
169 <div className="thinkrank-text-sm thinkrank-text-gray-600 thinkrank-leading-relaxed">
170 {truncatedDescription || __('Your meta description for mobile...', 'thinkrank')}
171 </div>
172 </div>
173 </div>
174 </div>
175 </div>
176 );
177 };
178
179 /**
180 * Combined SERP Preview with tabs
181 */
182 const CombinedSerpPreview = (props) => {
183 const [activeTab, setActiveTab] = useState('desktop');
184
185 return (
186 <div className="thinkrank-ui">
187 <div className="thinkrank-mb-4">
188 <div className="thinkrank-flex thinkrank-border-b thinkrank-border-gray-200">
189 <button
190 className={`thinkrank-px-4 thinkrank-py-2 thinkrank-text-sm thinkrank-font-medium thinkrank-border-b-2 ${
191 activeTab === 'desktop'
192 ? 'thinkrank-border-blue thinkrank-text-blue'
193 : 'thinkrank-border-transparent thinkrank-text-gray-500 hover:thinkrank-text-gray-700'
194 }`}
195 onClick={() => setActiveTab('desktop')}
196 >
197 {__('Desktop', 'thinkrank')}
198 </button>
199 <button
200 className={`thinkrank-px-4 thinkrank-py-2 thinkrank-text-sm thinkrank-font-medium thinkrank-border-b-2 ${
201 activeTab === 'mobile'
202 ? 'thinkrank-border-blue thinkrank-text-blue'
203 : 'thinkrank-border-transparent thinkrank-text-gray-500 hover:thinkrank-text-gray-700'
204 }`}
205 onClick={() => setActiveTab('mobile')}
206 >
207 {__('Mobile', 'thinkrank')}
208 </button>
209 </div>
210 </div>
211
212 {activeTab === 'desktop' ? (
213 <SerpPreview {...props} />
214 ) : (
215 <MobileSerpPreview {...props} />
216 )}
217 </div>
218 );
219 };
220
221 export default SerpPreview;
222 export { MobileSerpPreview, CombinedSerpPreview };
223