Admin UI


目录

The WordPress admin surface has three generations living side by side. Knowing which one you're in determines everything else — which framework, which data layer, and whether a fix needs applying twice.

Three generations

Where Status
Legacy screens includes/admin/ Maintained, still selectable by users
Modern React pages src/<Domain>/ + src/Views/Components/ Where new work goes
Settings includes/admin/settings/, src/Settings/ PHP only — no React equivalent exists

Only the four archive screens have two versions. Donations, Donors, Subscriptions, and Donation Forms each ship a modern React page and the legacy screen it replaced, selected per user — see list-tables.md for the switching mechanism.

Settings are a different story. There is no React settings page anywhere in the plugin. Every settings tab is PHP, built on includes/admin/settings/class-settings-*.php and the abstract-admin-settings-page.php base. src/Settings/Security/SecuritySettingsPage.php is modern code following the legacy settings pattern — don't read its location under src/ as meaning it's React. Adding a settings field means working in the PHP settings API.

Registering an admin page

Modern pages are a PHP class per domain — src/<Domain>/<Domain>AdminPage.php — with a consistent shape:

public function registerMenuItem()          // add_submenu_page, capability check
public function loadScripts()               // enqueue the built bundle, localize config
public function render()                    // print the mount point for React
public static function isShowing(): bool    // are we on this page right now?
public static function isShowingDetailsPage(): bool

The domain's ServiceProvider wires registerMenuItem() to admin_menu. For the four archive screens that registration is conditional on the user's legacy preference.

isShowing() / isShowingDetailsPage() matter more than they look — they gate asset loading, and enqueueing a heavy admin bundle unconditionally is a real performance cost on every admin page.

loadScripts() is where localization happens, and it is subject to the rules in pii.md: pass configuration and an API root, never records.

The details page framework

src/Views/Components/AdminDetailsPage/ is a generic, reusable details page, used by Campaign, Donation, Donor, Subscription, and Event Tickets. If you're building a sixth, use it — don't start from a copy of one of the five.

<AdminDetailsPage
    objectId={donation.id}
    objectType="donation"
    objectTypePlural="donations"
    useObjectEntityRecord={useDonationEntityRecord}
    tabDefinitions={tabDefinitions}
    breadcrumbUrl={…} pageTitle={…}
    StatusBadge={…} PrimaryActionButton={…} ContextMenuItems={…}
/>

What it provides, and what that implies:

Data: core-data entities, and the PII split

Admin React pages don't hand-roll fetching. GiveWP registers WordPress core-data entities so useEntityRecord / useDispatch(coreStore) work against the v3 REST API:

dispatch(coreStore).addEntities([
    {name: 'donation', kind: 'givewp', baseURL: '/givewp/v3/donations', plural: 'donations', …},
    // campaign, donor, subscription, form
]);

Each domain exposes a thin hook over this — useDonationEntityRecord, and siblings — which is what useObjectEntityRecord receives.

There are two entity bundles, and the difference is PII. RegisterAdminEntities loads entities-admin.ts; RegisterPublicEntities loads entities-public.ts. They register the same entities with different baseURLParams:

admin bundle public bundle
includeSensitiveData true false
anonymousDonations / anonymousDonors include redact

This is the admin/public boundary from pii.md enforced at the data layer, with safe defaults on the public side. Two consequences:

Shared components

src/Admin/ is a component library, not a domain — 38 TSX files, no PHP. Look here before writing anything from scratch:

Styling uses the design system foundation tokens rather than hardcoded values — see the Styles section of AGENTS.md.

Extension surface

AdminDetailsPage publishes a small API onto window.givewp.admin for add-ons:

window.givewp.admin.components  // AdminSection, AdminSectionField
window.givewp.admin.hooks       // useFormContext, useFormState

Combined with the PluginArea slot, that's how an add-on adds a section to a details page. Treat both as public API — renaming or removing an export there breaks add-ons silently.

Checklist


版本号 #2
由 壳壳虫 创建于 2026-08-29 22:50:57 UTC
由 壳壳虫 更新于 2026-08-29 22:50:57 UTC