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 / pages / Dashboard.js

Dashboard.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/pages/Dashboard.js

302 lines 19.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Dashboard Page Component
3 *
4 * Main dashboard for ThinkRank admin interface
5 *
6 * @package ThinkRank
7 * @since 1.0.0
8 */
9
10 import { useState, useEffect } from '@wordpress/element';
11 import { __ } from '@wordpress/i18n';
12 import { Card, CardBody, CardHeader, Spinner } from '@wordpress/components';
13 import apiFetch from '@wordpress/api-fetch';
14
15 // AI Provider Icons - Reusing from Settings page for consistency
16 const OpenAIIcon = () => (
17 <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
18 <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/>
19 </svg>
20 );
21
22 const ClaudeIcon = () => (
23 <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
24 <path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fillRule="nonzero"/>
25 </svg>
26 );
27
28 /**
29 * Dashboard Component
30 */
31 const Dashboard = () => {
32 return (
33 <div className="thinkrank-dashboard">
34 <OverviewTab />
35 </div>
36 );
37 };
38
39 /**
40 * Overview Tab Component
41 */
42 const OverviewTab = () => {
43 const [settings, setSettings] = useState(null);
44 const [briefCount, setBriefCount] = useState(0);
45 const [isLoading, setIsLoading] = useState(true);
46
47 // Helper function to check if API is actually configured
48 const isApiConfigured = (settings) => {
49 if (!settings?.ai_provider) return false;
50
51 const apiKeyField = settings.ai_provider === 'openai' ? 'openai_api_key' : 'claude_api_key';
52 const apiKey = settings[apiKeyField];
53
54 // Check if API key exists - masked keys (••••••••) indicate a saved key
55 return apiKey && apiKey.trim() && (apiKey.includes('••••') || apiKey.length > 10);
56 };
57
58 useEffect(() => {
59 const loadDashboardData = async () => {
60 try {
61 // Load settings to show API connection status
62 const settingsResponse = await apiFetch({
63 path: '/thinkrank/v1/settings',
64 method: 'GET',
65 });
66
67 setSettings(settingsResponse);
68
69 // Load brief count
70 const briefsResponse = await apiFetch({
71 path: '/thinkrank/v1/content-brief/list',
72 method: 'GET',
73 });
74 setBriefCount(briefsResponse?.data?.length || 0);
75
76 } catch (error) {
77 console.error('Failed to load dashboard data:', error);
78 } finally {
79 setIsLoading(false);
80 }
81 };
82
83 loadDashboardData();
84
85 // Also refresh when the component becomes visible (e.g., tab switch)
86 const handleVisibilityChange = () => {
87 if (!document.hidden) {
88 loadDashboardData();
89 }
90 };
91
92 document.addEventListener('visibilitychange', handleVisibilityChange);
93
94 return () => {
95 document.removeEventListener('visibilitychange', handleVisibilityChange);
96 };
97 }, []);
98
99 if (isLoading) {
100 return (
101 <div className="thinkrank-flex-center thinkrank-p-8">
102 <Spinner />
103 </div>
104 );
105 }
106
107 // Professional icons for dashboard cards
108 const CheckIcon = () => (
109 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
110 <path d="M20 6L9 17l-5-5"/>
111 </svg>
112 );
113
114 const DocumentIcon = () => (
115 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
116 <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
117 <polyline points="14,2 14,8 20,8"/>
118 </svg>
119 );
120
121 const StatusIcon = () => (
122 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
123 <circle cx="12" cy="12" r="10"/>
124 <path d="M9 12l2 2 4-4"/>
125 </svg>
126 );
127
128 return (
129 <div className="thinkrank-ui">
130 <div className="thinkrank-card thinkrank-card--elevated thinkrank-mb-6">
131 <div className="thinkrank-card__header">
132 <h2 className="thinkrank-text-2xl thinkrank-font-bold thinkrank-text-primary thinkrank-mb-2">
133 {__('Dashboard Overview', 'thinkrank')}
134 </h2>
135 <p className="thinkrank-text-secondary thinkrank-text-base">
136 {__('Your AI-powered content brief generator status and statistics.', 'thinkrank')}
137 </p>
138 </div>
139 <div className="thinkrank-card__body">
140 <div className="thinkrank-grid thinkrank-grid-cols-1-xs thinkrank-grid-cols-2-md thinkrank-grid-cols-3-lg thinkrank-gap-6">
141 {/* AI Status Card */}
142 <div className={`thinkrank-card thinkrank-card--${settings?.ai_provider === 'claude' ? 'warning' : settings?.ai_provider === 'openai' ? 'success' : 'flat'} thinkrank-relative`}>
143 {/* Status Badge */}
144 <div className="thinkrank-absolute thinkrank-top-3 thinkrank-right-3 thinkrank-z-10">
145 <span className={`thinkrank-px-2 thinkrank-py-1 thinkrank-text-xs thinkrank-font-medium thinkrank-rounded-full ${
146 isApiConfigured(settings)
147 ? 'thinkrank-bg-green thinkrank-text-white'
148 : 'thinkrank-bg-gray-200 thinkrank-text-gray-700'
149 }`}>
150 {isApiConfigured(settings) ? __('Connected', 'thinkrank') : __('Not Connected', 'thinkrank')}
151 </span>
152 </div>
153
154 <div className="thinkrank-card__body">
155 <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-4">
156 <div className={`thinkrank-w-12 thinkrank-h-12 thinkrank-rounded-lg thinkrank-flex-center thinkrank-p-3 ${
157 settings?.ai_provider === 'claude'
158 ? 'thinkrank-bg-orange thinkrank-text-white'
159 : settings?.ai_provider === 'openai'
160 ? 'thinkrank-bg-green thinkrank-text-white'
161 : 'thinkrank-bg-gray-100 thinkrank-text-gray-500'
162 }`}>
163 {isApiConfigured(settings) && settings?.ai_provider === 'claude' ? (
164 <ClaudeIcon />
165 ) : isApiConfigured(settings) && settings?.ai_provider === 'openai' ? (
166 <OpenAIIcon />
167 ) : (
168 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
169 <circle cx="12" cy="12" r="10"/>
170 <path d="M15 9l-6 6M9 9l6 6"/>
171 </svg>
172 )}
173 </div>
174 <div className="thinkrank-flex-1">
175 <h3 className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-muted thinkrank-uppercase thinkrank-tracking-wide thinkrank-mb-1">
176 {__('AI Provider', 'thinkrank')}
177 </h3>
178 {isApiConfigured(settings) ? (
179 <>
180 <div className="thinkrank-text-lg thinkrank-font-bold thinkrank-text-primary thinkrank-mb-1">
181 {settings.ai_provider === 'openai' ? 'OpenAI' : (settings.ai_provider === 'claude' ? 'Claude' : 'Gemini')}
182 </div>
183 <div className="thinkrank-text-sm thinkrank-text-secondary">
184 {(() => {
185 const model = settings.ai_provider === 'claude' ? settings.claude_model : (settings.ai_provider === 'gemini' ? settings.gemini_model : settings.openai_model);
186
187 // Use shared ModelBadge display names
188 switch (model) {
189 case 'gpt-5-nano': return 'GPT‑5‑nano';
190 case 'gpt-5-mini': return 'GPT‑5‑mini';
191 case 'gpt-5': return 'GPT‑5';
192 case 'gpt-4o': return 'GPT‑4o';
193 case 'claude-sonnet-4-0':
194 case 'claude-sonnet-4-20250514': return 'Claude 4 Sonnet';
195 case 'claude-opus-4-0':
196 case 'claude-opus-4-20250514': return 'Claude 4 Opus';
197 case 'claude-3-7-sonnet-latest':
198 case 'claude-3-7-sonnet-20250219': return 'Claude 3.7 Sonnet';
199 case 'claude-3-5-sonnet-latest':
200 case 'claude-3-5-sonnet-20241022': return 'Claude 3.5 Sonnet';
201 case 'claude-3-5-haiku-latest':
202 case 'claude-3-5-haiku-20241022': return 'Claude 3.5 Haiku';
203 case 'gemini-2.5-flash': return 'Gemini 2.5 Flash';
204 case 'gemini-2.5-flash-lite': return 'Gemini 2.5 Flash-Lite';
205 case 'gemini-2.5-pro': return 'Gemini 2.5 Pro';
206 case 'gemini-2.0-flash': return 'Gemini 2.0 Flash';
207 case 'gemini-1.5-flash': return 'Gemini 1.5 Flash';
208 default: return model;
209 }
210 return model || 'Not configured';
211 })()}
212 </div>
213 </>
214 ) : (
215 <>
216 <div className="thinkrank-text-lg thinkrank-font-bold thinkrank-text-primary thinkrank-mb-1">
217 {__('Not Configured', 'thinkrank')}
218 </div>
219 <div className="thinkrank-text-sm thinkrank-text-secondary">
220 {__('Configure API settings', 'thinkrank')}
221 </div>
222 </>
223 )}
224 </div>
225 </div>
226 </div>
227 </div>
228
229 {/* Content Briefs Card */}
230 <div className="thinkrank-card thinkrank-relative">
231 {/* Status Badge */}
232 <div className="thinkrank-absolute thinkrank-top-4 thinkrank-right-4">
233 <span className="thinkrank-px-2 thinkrank-py-1 thinkrank-text-xs thinkrank-font-medium thinkrank-rounded-full thinkrank-bg-blue thinkrank-text-white">
234 {briefCount > 0 ? __('Active', 'thinkrank') : __('Empty', 'thinkrank')}
235 </span>
236 </div>
237
238 <div className="thinkrank-card__body">
239 <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-4">
240 <div className="thinkrank-w-12 thinkrank-h-12 thinkrank-rounded-lg thinkrank-bg-blue thinkrank-text-white thinkrank-flex-center thinkrank-p-3">
241 <DocumentIcon />
242 </div>
243 <div className="thinkrank-flex-1">
244 <h3 className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-muted thinkrank-uppercase thinkrank-tracking-wide thinkrank-mb-1">
245 {__('Content Briefs', 'thinkrank')}
246 </h3>
247 <div className="thinkrank-text-3xl thinkrank-font-bold thinkrank-text-primary thinkrank-mb-1">
248 {briefCount}
249 </div>
250 <div className="thinkrank-text-sm thinkrank-text-secondary">
251 {__('Total Created', 'thinkrank')}
252 </div>
253 </div>
254 </div>
255 </div>
256 </div>
257
258 {/* System Status Card */}
259 <div className={`thinkrank-card thinkrank-card--${isApiConfigured(settings) ? 'success' : 'warning'} thinkrank-relative`}>
260 {/* Status Badge */}
261 <div className="thinkrank-absolute thinkrank-top-4 thinkrank-right-4">
262 <span className={`thinkrank-px-2 thinkrank-py-1 thinkrank-text-xs thinkrank-font-medium thinkrank-rounded-full ${
263 isApiConfigured(settings)
264 ? 'thinkrank-bg-green thinkrank-text-white'
265 : 'thinkrank-bg-orange thinkrank-text-white'
266 }`}>
267 {isApiConfigured(settings) ? __('Ready', 'thinkrank') : __('Setup Required', 'thinkrank')}
268 </span>
269 </div>
270
271 <div className="thinkrank-card__body">
272 <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-4">
273 <div className={`thinkrank-w-12 thinkrank-h-12 thinkrank-rounded-lg thinkrank-flex-center thinkrank-p-3 ${
274 isApiConfigured(settings)
275 ? 'thinkrank-bg-green thinkrank-text-white'
276 : 'thinkrank-bg-orange thinkrank-text-white'
277 }`}>
278 <StatusIcon />
279 </div>
280 <div className="thinkrank-flex-1">
281 <h3 className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-muted thinkrank-uppercase thinkrank-tracking-wide thinkrank-mb-1">
282 {__('System Status', 'thinkrank')}
283 </h3>
284 <div className="thinkrank-text-lg thinkrank-font-bold thinkrank-text-primary thinkrank-mb-1">
285 {isApiConfigured(settings) ? __('Operational', 'thinkrank') : __('Setup Required', 'thinkrank')}
286 </div>
287 <div className="thinkrank-text-sm thinkrank-text-secondary">
288 {isApiConfigured(settings) ? __('All systems running smoothly', 'thinkrank') : __('Configure API to get started', 'thinkrank')}
289 </div>
290 </div>
291 </div>
292 </div>
293 </div>
294 </div>
295 </div>
296 </div>
297 </div>
298 );
299 };
300
301 export default Dashboard;
302