# thinkrank/1.0.0/src/admin/components/pages/Dashboard.js

ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console &amp; Local SEO, version 1.0.0. 302 lines.

- Page: https://pluginprobe.com/plugins/thinkrank/1.0.0/code/src/admin/components/pages/Dashboard.js
- Raw: https://pluginprobe.com/plugins/thinkrank/1.0.0/raw/src/admin/components/pages/Dashboard.js
- Modified: 2025-08-10T10:57:26+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/thinkrank/1.0.0/code/src/admin/components/pages/Dashboard.js#L10-L20`.

```javascript
/**
 * Dashboard Page Component
 * 
 * Main dashboard for ThinkRank admin interface
 * 
 * @package ThinkRank
 * @since 1.0.0
 */

import { useState, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Card, CardBody, CardHeader, Spinner } from '@wordpress/components';
import apiFetch from '@wordpress/api-fetch';

// AI Provider Icons - Reusing from Settings page for consistency
const OpenAIIcon = () => (
    <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
        <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"/>
    </svg>
);

const ClaudeIcon = () => (
    <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
        <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"/>
    </svg>
);

/**
 * Dashboard Component
 */
const Dashboard = () => {
    return (
        <div className="thinkrank-dashboard">
            <OverviewTab />
        </div>
    );
};

/**
 * Overview Tab Component
 */
const OverviewTab = () => {
    const [settings, setSettings] = useState(null);
    const [briefCount, setBriefCount] = useState(0);
    const [isLoading, setIsLoading] = useState(true);

    // Helper function to check if API is actually configured
    const isApiConfigured = (settings) => {
        if (!settings?.ai_provider) return false;

        const apiKeyField = settings.ai_provider === 'openai' ? 'openai_api_key' : 'claude_api_key';
        const apiKey = settings[apiKeyField];

        // Check if API key exists - masked keys (••••••••) indicate a saved key
        return apiKey && apiKey.trim() && (apiKey.includes('••••') || apiKey.length > 10);
    };

    useEffect(() => {
        const loadDashboardData = async () => {
            try {
                // Load settings to show API connection status
                const settingsResponse = await apiFetch({
                    path: '/thinkrank/v1/settings',
                    method: 'GET',
                });

                setSettings(settingsResponse);

                // Load brief count
                const briefsResponse = await apiFetch({
                    path: '/thinkrank/v1/content-brief/list',
                    method: 'GET',
                });
                setBriefCount(briefsResponse?.data?.length || 0);

            } catch (error) {
                console.error('Failed to load dashboard data:', error);
            } finally {
                setIsLoading(false);
            }
        };

        loadDashboardData();

        // Also refresh when the component becomes visible (e.g., tab switch)
        const handleVisibilityChange = () => {
            if (!document.hidden) {
                loadDashboardData();
            }
        };

        document.addEventListener('visibilitychange', handleVisibilityChange);

        return () => {
            document.removeEventListener('visibilitychange', handleVisibilityChange);
        };
    }, []);

    if (isLoading) {
        return (
            <div className="thinkrank-flex-center thinkrank-p-8">
                <Spinner />
            </div>
        );
    }

    // Professional icons for dashboard cards
    const CheckIcon = () => (
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
            <path d="M20 6L9 17l-5-5"/>
        </svg>
    );

    const DocumentIcon = () => (
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
            <polyline points="14,2 14,8 20,8"/>
        </svg>
    );

    const StatusIcon = () => (
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
            <circle cx="12" cy="12" r="10"/>
            <path d="M9 12l2 2 4-4"/>
        </svg>
    );

    return (
        <div className="thinkrank-ui">
            <div className="thinkrank-card thinkrank-card--elevated thinkrank-mb-6">
                <div className="thinkrank-card__header">
                    <h2 className="thinkrank-text-2xl thinkrank-font-bold thinkrank-text-primary thinkrank-mb-2">
                        {__('Dashboard Overview', 'thinkrank')}
                    </h2>
                    <p className="thinkrank-text-secondary thinkrank-text-base">
                        {__('Your AI-powered content brief generator status and statistics.', 'thinkrank')}
                    </p>
                </div>
                <div className="thinkrank-card__body">
                    <div className="thinkrank-grid thinkrank-grid-cols-1-xs thinkrank-grid-cols-2-md thinkrank-grid-cols-3-lg thinkrank-gap-6">
                        {/* AI Status Card */}
                        <div className={`thinkrank-card thinkrank-card--${settings?.ai_provider === 'claude' ? 'warning' : settings?.ai_provider === 'openai' ? 'success' : 'flat'} thinkrank-relative`}>
                            {/* Status Badge */}
                            <div className="thinkrank-absolute thinkrank-top-3 thinkrank-right-3 thinkrank-z-10">
                                <span className={`thinkrank-px-2 thinkrank-py-1 thinkrank-text-xs thinkrank-font-medium thinkrank-rounded-full ${
                                    isApiConfigured(settings)
                                        ? 'thinkrank-bg-green thinkrank-text-white'
                                        : 'thinkrank-bg-gray-200 thinkrank-text-gray-700'
                                }`}>
                                    {isApiConfigured(settings) ? __('Connected', 'thinkrank') : __('Not Connected', 'thinkrank')}
                                </span>
                            </div>

                            <div className="thinkrank-card__body">
                                <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-4">
                                    <div className={`thinkrank-w-12 thinkrank-h-12 thinkrank-rounded-lg thinkrank-flex-center thinkrank-p-3 ${
                                        settings?.ai_provider === 'claude'
                                            ? 'thinkrank-bg-orange thinkrank-text-white'
                                            : settings?.ai_provider === 'openai'
                                            ? 'thinkrank-bg-green thinkrank-text-white'
                                            : 'thinkrank-bg-gray-100 thinkrank-text-gray-500'
                                    }`}>
                                        {isApiConfigured(settings) && settings?.ai_provider === 'claude' ? (
                                            <ClaudeIcon />
                                        ) : isApiConfigured(settings) && settings?.ai_provider === 'openai' ? (
                                            <OpenAIIcon />
                                        ) : (
                                            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                                                <circle cx="12" cy="12" r="10"/>
                                                <path d="M15 9l-6 6M9 9l6 6"/>
                                            </svg>
                                        )}
                                    </div>
                                    <div className="thinkrank-flex-1">
                                        <h3 className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-muted thinkrank-uppercase thinkrank-tracking-wide thinkrank-mb-1">
                                            {__('AI Provider', 'thinkrank')}
                                        </h3>
                                        {isApiConfigured(settings) ? (
                                            <>
                                                <div className="thinkrank-text-lg thinkrank-font-bold thinkrank-text-primary thinkrank-mb-1">
                                                    {settings.ai_provider === 'openai' ? 'OpenAI' : (settings.ai_provider === 'claude' ? 'Claude' : 'Gemini')}
                                                </div>
                                                <div className="thinkrank-text-sm thinkrank-text-secondary">
                                                    {(() => {
                                                        const model = settings.ai_provider === 'claude' ? settings.claude_model : (settings.ai_provider === 'gemini' ? settings.gemini_model : settings.openai_model);

                                                        // Use shared ModelBadge display names
                                                        switch (model) {
                                                            case 'gpt-5-nano': return 'GPT‑5‑nano';
                                                            case 'gpt-5-mini': return 'GPT‑5‑mini';
                                                            case 'gpt-5': return 'GPT‑5';
                                                            case 'gpt-4o': return 'GPT‑4o';
                                                            case 'claude-sonnet-4-0':
                                                            case 'claude-sonnet-4-20250514': return 'Claude 4 Sonnet';
                                                            case 'claude-opus-4-0':
                                                            case 'claude-opus-4-20250514': return 'Claude 4 Opus';
                                                            case 'claude-3-7-sonnet-latest':
                                                            case 'claude-3-7-sonnet-20250219': return 'Claude 3.7 Sonnet';
                                                            case 'claude-3-5-sonnet-latest':
                                                            case 'claude-3-5-sonnet-20241022': return 'Claude 3.5 Sonnet';
                                                            case 'claude-3-5-haiku-latest':
                                                            case 'claude-3-5-haiku-20241022': return 'Claude 3.5 Haiku';
                                                            case 'gemini-2.5-flash': return 'Gemini 2.5 Flash';
                                                            case 'gemini-2.5-flash-lite': return 'Gemini 2.5 Flash-Lite';
                                                            case 'gemini-2.5-pro': return 'Gemini 2.5 Pro';
                                                            case 'gemini-2.0-flash': return 'Gemini 2.0 Flash';
                                                            case 'gemini-1.5-flash': return 'Gemini 1.5 Flash';
                                                            default: return model;
                                                        }
                                                        return model || 'Not configured';
                                                    })()}
                                                </div>
                                            </>
                                        ) : (
                                            <>
                                                <div className="thinkrank-text-lg thinkrank-font-bold thinkrank-text-primary thinkrank-mb-1">
                                                    {__('Not Configured', 'thinkrank')}
                                                </div>
                                                <div className="thinkrank-text-sm thinkrank-text-secondary">
                                                    {__('Configure API settings', 'thinkrank')}
                                                </div>
                                            </>
                                        )}
                                    </div>
                                </div>
                            </div>
                        </div>

                        {/* Content Briefs Card */}
                        <div className="thinkrank-card thinkrank-relative">
                            {/* Status Badge */}
                            <div className="thinkrank-absolute thinkrank-top-4 thinkrank-right-4">
                                <span className="thinkrank-px-2 thinkrank-py-1 thinkrank-text-xs thinkrank-font-medium thinkrank-rounded-full thinkrank-bg-blue thinkrank-text-white">
                                    {briefCount > 0 ? __('Active', 'thinkrank') : __('Empty', 'thinkrank')}
                                </span>
                            </div>

                            <div className="thinkrank-card__body">
                                <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-4">
                                    <div className="thinkrank-w-12 thinkrank-h-12 thinkrank-rounded-lg thinkrank-bg-blue thinkrank-text-white thinkrank-flex-center thinkrank-p-3">
                                        <DocumentIcon />
                                    </div>
                                    <div className="thinkrank-flex-1">
                                        <h3 className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-muted thinkrank-uppercase thinkrank-tracking-wide thinkrank-mb-1">
                                            {__('Content Briefs', 'thinkrank')}
                                        </h3>
                                        <div className="thinkrank-text-3xl thinkrank-font-bold thinkrank-text-primary thinkrank-mb-1">
                                            {briefCount}
                                        </div>
                                        <div className="thinkrank-text-sm thinkrank-text-secondary">
                                            {__('Total Created', 'thinkrank')}
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>

                        {/* System Status Card */}
                        <div className={`thinkrank-card thinkrank-card--${isApiConfigured(settings) ? 'success' : 'warning'} thinkrank-relative`}>
                            {/* Status Badge */}
                            <div className="thinkrank-absolute thinkrank-top-4 thinkrank-right-4">
                                <span className={`thinkrank-px-2 thinkrank-py-1 thinkrank-text-xs thinkrank-font-medium thinkrank-rounded-full ${
                                    isApiConfigured(settings)
                                        ? 'thinkrank-bg-green thinkrank-text-white'
                                        : 'thinkrank-bg-orange thinkrank-text-white'
                                }`}>
                                    {isApiConfigured(settings) ? __('Ready', 'thinkrank') : __('Setup Required', 'thinkrank')}
                                </span>
                            </div>

                            <div className="thinkrank-card__body">
                                <div className="thinkrank-flex thinkrank-items-center thinkrank-gap-4">
                                    <div className={`thinkrank-w-12 thinkrank-h-12 thinkrank-rounded-lg thinkrank-flex-center thinkrank-p-3 ${
                                        isApiConfigured(settings)
                                            ? 'thinkrank-bg-green thinkrank-text-white'
                                            : 'thinkrank-bg-orange thinkrank-text-white'
                                    }`}>
                                        <StatusIcon />
                                    </div>
                                    <div className="thinkrank-flex-1">
                                        <h3 className="thinkrank-text-xs thinkrank-font-semibold thinkrank-text-muted thinkrank-uppercase thinkrank-tracking-wide thinkrank-mb-1">
                                            {__('System Status', 'thinkrank')}
                                        </h3>
                                        <div className="thinkrank-text-lg thinkrank-font-bold thinkrank-text-primary thinkrank-mb-1">
                                            {isApiConfigured(settings) ? __('Operational', 'thinkrank') : __('Setup Required', 'thinkrank')}
                                        </div>
                                        <div className="thinkrank-text-sm thinkrank-text-secondary">
                                            {isApiConfigured(settings) ? __('All systems running smoothly', 'thinkrank') : __('Configure API to get started', 'thinkrank')}
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
};

export default Dashboard;

```
