import { useEffect, useState, type ReactNode } from 'react'; import { BotIcon, HeadingIcon, LayoutGridIcon, MapIcon, type LucideIcon, } from 'lucide-react'; import { Link } from 'react-router-dom'; import { classNames } from '../../lib/utils'; import { fetchLlmHits } from '../../service/llm/llm.service'; import type { ISeoSettings } from '../../service/seo/seo.interface'; import { Card, CardContent } from '../ui/card'; const StatusCard = ({ icon: Icon, label, value, tone, href, to, children, }: { icon: LucideIcon; label: string; value: string; tone: 'on' | 'off' | 'yield'; /** An address the value opens in a new tab. */ href?: string; /** A page of the app the value leads to. */ to?: string; children: ReactNode; }): JSX.Element => (
{label} {href ? ( {value} ) : to ? ( {value} ) : ( {value} )}

{children}

); /** * What the head of every page carries: the titles and meta module and the * schema module each print their part, and both yield to another SEO plugin. */ export function headStatus({ outputEnabled, rival, headOn, schemaOn, }: { outputEnabled: boolean; rival: string; headOn: boolean; schemaOn: boolean; }): { value: string; text: string; tone: 'on' | 'off' | 'yield' } { if (!outputEnabled) { return { value: `Yielding to ${rival}`, text: 'Recomaze stays silent so nothing is printed twice.', tone: 'yield', }; } if (!headOn && !schemaOn) { return { value: 'Off', text: 'The titles and meta and the schema modules are off: WordPress prints its own title and nothing else.', tone: 'off', }; } if (!headOn) { return { value: 'Printed by Recomaze', text: 'Schema only: the titles and meta module is off, so WordPress prints its own title.', tone: 'on', }; } return { value: 'Printed by Recomaze', text: schemaOn ? 'Title, description, robots, canonical, Open Graph and schema on every page.' : 'Title, description, robots, canonical and Open Graph on every page; the schema module is off.', tone: 'on', }; } /** * How the AI engines read the site: the requests AI crawlers made for * llms.txt in the last month, with the way to the file and the log. */ const AiAccessCard = (): JSX.Element => { const [count, setCount] = useState(null); useEffect(() => { fetchLlmHits(30) .then(hits => setCount( hits.bot_counts .filter( row => row.kind === 'answers' || row.kind === 'training' || row.kind === 'mixed' ) .reduce((sum, row) => sum + row.count, 0) ) ) .catch(() => setCount(null)); }, []); return ( {count === null ? 'What AI crawlers read of the site, and who has been reading it.' : 'To llms.txt in the last 30 days. Which AI crawlers may read the site is set under Edit robots.txt.'} ); }; /** * What the module is doing right now, in four tiles a merchant can read * without opening a single setting. */ export const StatusStrip = ({ settings, }: { settings: ISeoSettings; }): JSX.Element => { const { conflicts, sitemap, modules } = settings; const active = Object.values(modules).filter(module => module.active).length; const rival = conflicts.active .map(plugin => `${plugin.name} ${plugin.version ?? ''}`.trim()) .join(', '); const sitemapOn = sitemap.enabled && conflicts.output_enabled; const head = headStatus({ outputEnabled: conflicts.output_enabled, rival, headOn: Boolean(modules.head?.active), schemaOn: Boolean(modules.schema?.active), }); return (
{/* Two by two in a narrow column, so the modules stay in view. */}
{head.text} {sitemapOn ? `${sitemap.links_per_sitemap} links per file${ sitemap.include_images ? ', with images' : '' }.` : sitemap.enabled ? 'Kept off while another SEO plugin serves its own sitemap.' : 'WordPress serves its core sitemap at /wp-sitemap.xml instead.'} 0 ? 'on' : 'off'} > {Object.values(modules) .filter(module => module.active) .map(module => module.label) .join(', ') || 'Every module is off.'}
); };