# thinkrank/1.0.0/src/admin/components/seo/ScoreBreakdown.js

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

- Page: https://pluginprobe.com/plugins/thinkrank/1.0.0/code/src/admin/components/seo/ScoreBreakdown.js
- Raw: https://pluginprobe.com/plugins/thinkrank/1.0.0/raw/src/admin/components/seo/ScoreBreakdown.js
- Modified: 2025-08-10T07:57:44+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/seo/ScoreBreakdown.js#L10-L20`.

```javascript
/**
 * SEO Score Breakdown Component
 * 
 * Displays detailed breakdown of the 10-factor SEO scoring system
 * 
 * @package ThinkRank
 * @since 1.0.0
 */

import { __ } from '@wordpress/i18n';
import { useState, useEffect } from '@wordpress/element';
import { Card, CardBody, CardHeader, ProgressBar, Button, Flex, FlexItem } from '@wordpress/components';
import { chevronDown, chevronUp } from '@wordpress/icons';

/**
 * Score Breakdown Component
 *
 * @param {Object} props Component props
 * @param {Object} props.scoreData Complete scoring data from API
 * @param {Function} props.onRefresh Callback for refreshing scores (optional)
 * @param {boolean} props.showOverview Whether to show the overview card (default: true)
 * @returns {JSX.Element} Score breakdown component
 */
const ScoreBreakdown = ({ scoreData, onRefresh = null, showOverview = true }) => {
    const [expandedSections, setExpandedSections] = useState({});
    const [isRefreshing, setIsRefreshing] = useState(false);

    // 2025 SEO Algorithm Factors (Google Q1 2025 Update)
    // Based on First Page Sage research and latest Google algorithm changes
    const scoreFactors = {
        satisfying_content: {
            label: __('Satisfying Content', 'thinkrank'),
            description: __('#1 ranking factor - Consistent publication of satisfying content (23%)', 'thinkrank'),
            maxScore: 23,
            icon: '🎯'
        },
        title_optimization: {
            label: __('Title Optimization', 'thinkrank'),
            description: __('Keyword in meta title with looser matching requirements (14%)', 'thinkrank'),
            maxScore: 14,
            icon: '📝'
        },
        niche_expertise: {
            label: __('Niche Expertise', 'thinkrank'),
            description: __('Hub & spoke content clusters demonstrating expertise (13%)', 'thinkrank'),
            maxScore: 13,
            icon: '🧠'
        },
        searcher_engagement: {
            label: __('Searcher Engagement', 'thinkrank'),
            description: __('Dwell time, bounce rate, pages per session metrics (12%)', 'thinkrank'),
            maxScore: 12,
            icon: '👥'
        },
        backlink_authority: {
            label: __('Backlink Authority', 'thinkrank'),
            description: __('Quality backlinks - declining but still important (13%)', 'thinkrank'),
            maxScore: 13,
            icon: '🔗'
        },
        content_freshness: {
            label: __('Content Freshness', 'thinkrank'),
            description: __('Quarterly content updates and maintenance (6%)', 'thinkrank'),
            maxScore: 6,
            icon: '🔄'
        },
        mobile_experience: {
            label: __('Mobile Experience Score', 'thinkrank'),
            description: __('NEW 2025: Mobile Experience Score (MES) replacing mobile-first (5%)', 'thinkrank'),
            maxScore: 5,
            icon: '📱'
        },
        trustworthiness: {
            label: __('Trustworthiness', 'thinkrank'),
            description: __('E-A-T signals and brand authority indicators (5%)', 'thinkrank'),
            maxScore: 5,
            icon: '🛡️'
        },
        link_diversity: {
            label: __('Link Distribution Diversity', 'thinkrank'),
            description: __('Diverse link profile and natural link patterns (4%)', 'thinkrank'),
            maxScore: 4,
            icon: '🌐'
        },
        core_web_vitals: {
            label: __('Core Web Vitals 2.0', 'thinkrank'),
            description: __('Updated 2025 performance metrics and user experience (3%)', 'thinkrank'),
            maxScore: 3,
            icon: '⚡'
        },
        site_security: {
            label: __('Site Security', 'thinkrank'),
            description: __('HTTPS, security headers, and trust signals (1%)', 'thinkrank'),
            maxScore: 1,
            icon: '🔒'
        },
        internal_linking: {
            label: __('Internal Linking', 'thinkrank'),
            description: __('Strategic internal link structure and content clusters (1%)', 'thinkrank'),
            maxScore: 1,
            icon: '🔗'
        }
    };

    /**
     * Toggle expanded state for a section
     */
    const toggleSection = (factorKey) => {
        setExpandedSections(prev => ({
            ...prev,
            [factorKey]: !prev[factorKey]
        }));
    };

    /**
     * Get score color class based on percentage
     */
    const getScoreColor = (score, maxScore) => {
        const percentage = (score / maxScore) * 100;
        if (percentage >= 90) return 'score-excellent';
        if (percentage >= 70) return 'score-good';
        if (percentage >= 50) return 'score-fair';
        return 'score-poor';
    };

    /**
     * Handle refresh scores
     */
    const handleRefresh = async () => {
        if (!onRefresh) return;

        setIsRefreshing(true);
        try {
            await onRefresh();
        } finally {
            setIsRefreshing(false);
        }
    };

    /**
     * Convert technical details to human-readable format
     */
    const formatFactorDetails = (factorKey, details) => {
        if (!details || typeof details !== 'object') return null;

        const formatters = {
            satisfying_content: (d) => [
                `Word count: ${d.word_count || 0} words (saved content)`,
                `Content satisfaction: ${d.intent_satisfaction || 0}%`,
                `Content uniqueness: ${d.uniqueness_score || 0}%`,
                `Content depth: ${d.content_depth || 'Basic'}`
            ],
            title_optimization: (d) => [
                `Title length: ${d.title_length || 0} characters`,
                `Keyword in title: ${d.keyword_in_title ? 'Yes' : 'No'}`,
                `Title optimization: ${d.title_score || 0}%`
            ],
            niche_expertise: (d) => [
                `Topic coverage: ${d.topic_coverage || 'Basic'}`,
                `Content depth: ${d.content_depth || 'Standard'}`,
                `Expertise signals: ${d.expertise_score || 0}%`
            ],
            searcher_engagement: (d) => [
                `Content engagement: ${d.engagement_score || 0}%`,
                `Reading time: ${d.estimated_reading_time || 'Unknown'}`,
                `User experience: ${d.ux_score || 'Good'}`
            ],
            backlink_authority: (d) => [
                `Authority score: ${d.authority_score || 0}%`,
                `Link quality: ${d.link_quality || 'Unknown'}`,
                `Domain authority: ${d.domain_authority || 'Not assessed'}`
            ],
            content_freshness: (d) => [
                `Freshness status: ${d.freshness_status || 'Current'}`,
                `Last updated: ${d.last_updated ? new Date(d.last_updated).toLocaleDateString() : 'Unknown'}`
            ],
            mobile_experience: (d) => [
                `Mobile score: ${d.mes_score || 'Good'}`,
                `Mobile friendly: ${d.mobile_friendly ? 'Yes' : 'No'}`,
                `Page speed: ${d.page_speed || 'Not tested'}`
            ],
            technical_factors: (d) => [
                `Meta description: ${d.meta_description_length || 0} characters`,
                `Schema markup: ${d.schema_present ? 'Present' : 'Missing'}`,
                `Technical score: ${d.technical_score || 0}%`
            ]
        };

        const formatter = formatters[factorKey];
        return formatter ? formatter(details) : null;
    };

    if (!scoreData || !scoreData.score_breakdown) {
        return (
            <Card>
                <CardBody>
                    <p>{__('No scoring data available. Run an SEO analysis to see detailed breakdown.', 'thinkrank')}</p>
                </CardBody>
            </Card>
        );
    }

    const { overall_score, score_breakdown, grade, calculated_at } = scoreData;

    return (
        <div className="thinkrank-score-breakdown">
            {/* Overall Score Header - Only show if showOverview is true */}
            {showOverview && (
                <Card className="score-overview-card">
                    <CardHeader>
                        <Flex justify="space-between" align="center">
                            <FlexItem>
                                <h3>{__('SEO Score Breakdown', 'thinkrank')}</h3>
                            </FlexItem>
                            {onRefresh && (
                                <FlexItem>
                                    <Button
                                        variant="secondary"
                                        onClick={handleRefresh}
                                        isBusy={isRefreshing}
                                        disabled={isRefreshing}
                                    >
                                        {__('Refresh', 'thinkrank')}
                                    </Button>
                                </FlexItem>
                            )}
                        </Flex>
                    </CardHeader>
                    <CardBody>
                        <div className="overall-score-display">
                            <div className={`score-circle ${getScoreColor(overall_score, 100)}`}>
                                <span className="score-number">{overall_score}</span>
                                <span className="score-grade">{grade}</span>
                            </div>
                            <div className="score-info">
                                <p className="score-description">
                                    {overall_score >= 80 && __('Excellent SEO optimization!', 'thinkrank')}
                                    {overall_score >= 60 && overall_score < 80 && __('Good SEO with room for improvement', 'thinkrank')}
                                    {overall_score >= 40 && overall_score < 60 && __('Fair SEO - needs attention', 'thinkrank')}
                                    {overall_score < 40 && __('Poor SEO - requires significant work', 'thinkrank')}
                                </p>
                                {calculated_at && (
                                    <p className="score-timestamp">
                                        {__('Last updated:', 'thinkrank')} {new Date(calculated_at).toLocaleString()}
                                    </p>
                                )}
                            </div>
                        </div>
                    </CardBody>
                </Card>
            )}

            {/* Detailed Factor Breakdown */}
            <Card className="score-factors-card">
                <CardHeader>
                    <h4>{__('Scoring Factors', 'thinkrank')}</h4>
                </CardHeader>
                <CardBody>
                    <div className="score-factors-list">
                        {Object.entries(scoreFactors).map(([factorKey, factorConfig]) => {
                            const factorData = score_breakdown[factorKey];
                            if (!factorData) return null;

                            const isExpanded = expandedSections[factorKey];
                            const percentage = (factorData.score / factorData.max_score) * 100;

                            return (
                                <div key={factorKey} className="score-factor-item">
                                    <div 
                                        className="factor-header"
                                        onClick={() => toggleSection(factorKey)}
                                        role="button"
                                        tabIndex={0}
                                    >
                                        <Flex justify="space-between" align="center">
                                            <FlexItem>
                                                <div className="factor-info">
                                                    <span className="factor-icon">{factorConfig.icon}</span>
                                                    <div className="factor-text">
                                                        <span className="factor-label">{factorConfig.label}</span>
                                                        <span className="factor-description">{factorConfig.description}</span>
                                                    </div>
                                                </div>
                                            </FlexItem>
                                            <FlexItem className="factor-score-container">
                                                <div className="factor-score">
                                                    <span className="score-value">
                                                        {factorData.score}/{factorData.max_score}
                                                    </span>
                                                    <div className="score-bar">
                                                        <div
                                                            className={`score-bar-fill ${getScoreColor(factorData.score, factorData.max_score)}`}
                                                            style={{ width: `${percentage}%` }}
                                                        />
                                                    </div>
                                                </div>
                                            </FlexItem>
                                            <FlexItem>
                                                <Button
                                                    variant="tertiary"
                                                    icon={isExpanded ? chevronUp : chevronDown}
                                                    size="small"
                                                />
                                            </FlexItem>
                                        </Flex>
                                    </div>

                                    {isExpanded && (
                                        <div className="factor-details">
                                            {factorData.suggestions && factorData.suggestions.length > 0 && (
                                                <div className="factor-suggestions">
                                                    <h5>{__('Suggestions:', 'thinkrank')}</h5>
                                                    <ul>
                                                        {factorData.suggestions.map((suggestion, index) => (
                                                            <li key={index}>{suggestion}</li>
                                                        ))}
                                                    </ul>
                                                </div>
                                            )}
                                            
                                            {factorData.details && (
                                                <div className="factor-technical-details">
                                                    <h5>{__('Details:', 'thinkrank')}</h5>
                                                    <div className="details-list">
                                                        {formatFactorDetails(factorKey, factorData.details)?.map((detail, index) => (
                                                            <div key={index} className="detail-item">
                                                                {detail}
                                                            </div>
                                                        )) || (
                                                            <div className="detail-item">
                                                                {__('Technical details available', 'thinkrank')}
                                                            </div>
                                                        )}
                                                    </div>
                                                </div>
                                            )}
                                        </div>
                                    )}
                                </div>
                            );
                        })}
                    </div>
                </CardBody>
            </Card>
        </div>
    );
};

export default ScoreBreakdown;

```
