import { createInterpolateElement, useState } from '@wordpress/element'; import { __, _n, sprintf } from '@wordpress/i18n'; import { HxCard, HxHead, HxRow, HxToggle, HxBadge, HxInp, HxGL, HxField, HxIcon, HxSelect, HxTag, HxCode } from '../components.jsx'; import { IC } from '../icons'; // WP Core Sync card. // Uses the same shared components as Design / Performance / Security: // HxCard + HxHead → card chrome // HxGL → section group labels // HxRow → key/value rows (consistent padding, divider, alignment) // HxBadge → status pills function ManageLink({ href, label }) { return ( {label || __( 'Manage', 'hatch-connect' )} {IC.externalLink} ); } function MenuSelect({ value, options, onChange, ariaLabel }) { return ( onChange(Number(e.target.value))} minWidth={200}> {options.map((mn) => ( ))} ); } function CoreSync({ data, content, core, setSetting, onDirty, guardTurnstile, tsNeedKeys }) { if (!data) return null; const { site, permalink, homepage, menus, all_menus, discussion, reading, privacy, post_types, taxonomies, languages, roles, authors } = data; const assignedCount = menus.filter(m => m.assigned_id > 0).length; const userTotal = roles.reduce((s, r) => s + r.count, 0); return ( {/* Site identity */} { __( 'Site identity', 'hatch-connect' ) }
{site.logo_url ? __( 'Logo set', 'hatch-connect' ) : __( 'No logo', 'hatch-connect' )} {site.favicon_url ? __( 'Favicon set', 'hatch-connect' ) : __( 'No favicon', 'hatch-connect' )}
{/* URL structure */} { __( 'URL structure', 'hatch-connect' ) } tags must stay. */ sprintf( __( 'Your frontend can find your posts. Post links look like %s.', 'hatch-connect' ), permalink.example ), { code: } ) : createInterpolateElement( __( 'WordPress is using the plain ?p=123 link format, which the frontend cannot read. Choose any other link format in Settings, Permalinks.', 'hatch-connect' ), { code: } )} last >
{permalink.pretty ? __( 'Readable links', 'hatch-connect' ) : __( 'Needs fixing', 'hatch-connect' )}
{/* Homepage and reading */} { __( 'Homepage & reading', 'hatch-connect' ) }
{homepage.mode === 'page' ? __( 'Static page', 'hatch-connect' ) : __( 'Latest posts', 'hatch-connect' )}
{reading.blog_public ? __( 'Public', 'hatch-connect' ) : __( 'Discouraged', 'hatch-connect' )}
{/* Menu locations, with an inline picker */} 0 ? 'green' : 'yellow'}>{ /* translators: 1: number of menu locations with a menu assigned, 2: total number of menu locations. */ sprintf( __( '%1$d/%2$d assigned', 'hatch-connect' ), assignedCount, menus.length || 0 ) }}>{ __( 'Menu locations', 'hatch-connect' ) } {menus.length === 0 && ( )} {menus.map((m, i) => ( 0 /* translators: 1: menu name, 2: number of items in the menu. */ ? sprintf( _n( 'Assigned: %1$s (%2$d item)', 'Assigned: %1$s (%2$d items)', m.count, 'hatch-connect' ), m.assigned, m.count ) /* translators: %s: menu name. */ : sprintf( __( 'Assigned: %s', 'hatch-connect' ), m.assigned ) ) : __( 'Choose a menu from the list.', 'hatch-connect' )} last={i === menus.length - 1 && all_menus.length > 0} > { setSetting(`core.menu_location.${m.loc}`, v); onDirty(); }} /> ))} {all_menus.length === 0 && menus.length > 0 && ( )} {/* Discussion and comments */} 0 ? 'yellow' : 'neutral'}>{ /* translators: 1: number of approved comments, 2: number of comments waiting for approval. */ sprintf( __( '%1$d approved, %2$d pending', 'hatch-connect' ), discussion.approved_count, discussion.pending_count ) }}>{ __( 'Discussion', 'hatch-connect' ) } { setSetting('content.comments_enabled', v); onDirty(); }} /> { setSetting('core.default_comment_status', v ? 'closed' : 'open'); onDirty(); }} /> {/* Frontend accounts: off until the site owner turns it on. */} { content.accounts_enabled ? __( 'On', 'hatch-connect' ) : __( 'Off', 'hatch-connect' ) }}>{ __( 'Accounts', 'hatch-connect' ) } { setSetting('content.accounts_enabled', v); onDirty(); }} ariaLabel={ __( 'Frontend accounts', 'hatch-connect' ) } /> {/* Content types */} {post_types.length}}>{ __( 'Content types', 'hatch-connect' ) }
{post_types.map((p) => ( {p.label} ))}
{/* Taxonomies */} {taxonomies.length}}>{ __( 'Taxonomies', 'hatch-connect' ) }
{taxonomies.map((t) => ( {t.label} ))}
{/* Users and roles */} { /* translators: %d: number of users. */ sprintf( _n( '%d user', '%d users', userTotal, 'hatch-connect' ), userTotal ) }}>{ __( 'Users & roles', 'hatch-connect' ) }
{roles.filter(r => r.count > 0).map(r => ( {r.name} ))}
0 ? sprintf( /* translators: 1: number of authors, 2: number of authors who have a bio. */ _n( '%1$d author, %2$d with a bio. Bios and avatars appear on the author pages of your frontend.', '%1$d authors, %2$d with a bio. Bios and avatars appear on the author pages of your frontend.', authors.total, 'hatch-connect' ), authors.total, authors.with_bio ) : __( 'No authors yet. Anyone who publishes a post will show up here.', 'hatch-connect' )} last >
{authors && authors.list.slice(0, 4).map((a) => ( {a.name} {!a.has_bio && <> { __( 'No bio', 'hatch-connect' ) }} ))} {authors && authors.total > 4 && ( { /* translators: %d: number of authors not shown. */ sprintf( __( '+%d more', 'hatch-connect' ), authors.total - 4 ) } )}
{/* Privacy */} { __( 'Privacy', 'hatch-connect' ) }
{privacy.page_id ? __( 'Set', 'hatch-connect' ) : __( 'Not set', 'hatch-connect' )}
{/* Languages */} { __( 'Languages', 'hatch-connect' ) } 0 ? __( 'Multilingual site', 'hatch-connect' ) : __( 'Single-language site', 'hatch-connect' )} desc={languages.length > 0 /* translators: %s: comma-separated list of language codes. */ ? sprintf( __( 'Languages found: %s.', 'hatch-connect' ), languages.map(l => l.code).join(', ') ) : __( 'Hatch lists languages here when Polylang or WPML is installed.', 'hatch-connect' )} last > 0 ? 'green' : 'neutral'}>{languages.length > 0 /* translators: %d: number of languages. */ ? sprintf( _n( '%d language', '%d languages', languages.length, 'hatch-connect' ), languages.length ) : __( 'Single', 'hatch-connect' )}
); } export default function Content({ state, onDirty, setSetting }) { const snippets = state.snippets || {}; const content = state.content || {}; const ts = state.turnstile || {}; const coreSync = state.coreSync || null; const onText = (path) => (e) => { setSetting(path, e.target.value); onDirty(); }; // Turnstile gating: turning Turnstile on without keys does nothing useful // (the frontend widget never renders and the server never verifies). Refuse // the flip, scroll to the key inputs, and flash the section so it is obvious // where to go next. const hasKeys = !!(ts.site_key && ts.secret_key); const [tsNeedKeys, setTsNeedKeys] = useState(false); const guardTurnstile = (path) => (v) => { if (v && !hasKeys) { setTsNeedKeys(true); const el = document.getElementById('hatch-turnstile-keys'); if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); el.classList.remove('hatch-flash'); // force reflow so the animation restarts on repeated clicks void el.offsetWidth; el.classList.add('hatch-flash'); const input = el.querySelector('input:not([type="password"])'); if (input) setTimeout(() => input.focus(), 350); } return; // do NOT flip the toggle, do NOT mark dirty } setSetting(path, v); onDirty(); }; return (
{/* WP Core Sync sits at the top: one status view of every WordPress-owned setting Hatch syncs. The comment toggles live inside its Discussion section, next to WordPress's own comment settings. */} {/* Third-party keys and services. Google Tag Manager only: GA4, Plausible and Pixel are managed as tags inside the GTM container, so there are no separate fields for them. */} { __( 'Google Tag Manager (analytics)', 'hatch-connect' ) }
{ setSetting('snippets.gtm_id', e.target.value); onDirty(); }} pattern="GTM-[A-Z0-9]+" autoComplete="off" spellCheck="false" />
{ __( 'Cloudflare Turnstile (spam protection)', 'hatch-connect' ) }
{ __( 'One pair of keys covers every place Turnstile is turned on: frontend comments, the WordPress sign-in page and the WordPress comment form.', 'hatch-connect' ) }
{hasKeys ? __( 'Configured', 'hatch-connect' ) : __( 'Keys missing', 'hatch-connect' )}
{tsNeedKeys && !hasKeys && (

{ __( 'Add both keys below, then turn Turnstile on.', 'hatch-connect' ) }

)}
); }